diff options
Diffstat (limited to 'media')
46 files changed, 1404 insertions, 1824 deletions
diff --git a/media/base/constants.cc b/media/base/constants.cc index cd10ef7..19a960f 100644 --- a/media/base/constants.cc +++ b/media/base/constants.cc @@ -59,6 +59,7 @@ const char kCodecParamSPropStereo[] = "sprop-stereo"; const char kCodecParamStereo[] = "stereo"; const char kCodecParamUseInbandFec[] = "useinbandfec"; const char kCodecParamMaxAverageBitrate[] = "maxaveragebitrate"; +const char kCodecParamMaxPlaybackRate[] = "maxplaybackrate"; const char kCodecParamSctpProtocol[] = "protocol"; const char kCodecParamSctpStreams[] = "streams"; @@ -72,6 +73,7 @@ const int kOpusDefaultMinPTime = 3; const int kOpusDefaultSPropStereo = 0; const int kOpusDefaultStereo = 0; const int kOpusDefaultUseInbandFec = 0; +const int kOpusDefaultMaxPlaybackRate = 48000; const int kPreferredMaxPTime = 60; const int kPreferredMinPTime = 10; diff --git a/media/base/constants.h b/media/base/constants.h index 5ac1be2..5168acb 100644 --- a/media/base/constants.h +++ b/media/base/constants.h @@ -62,6 +62,7 @@ extern const char kCodecParamSPropStereo[]; extern const char kCodecParamStereo[]; extern const char kCodecParamUseInbandFec[]; extern const char kCodecParamMaxAverageBitrate[]; +extern const char kCodecParamMaxPlaybackRate[]; extern const char kCodecParamSctpProtocol[]; extern const char kCodecParamSctpStreams[]; @@ -79,6 +80,8 @@ extern const int kOpusDefaultMinPTime; extern const int kOpusDefaultSPropStereo; extern const int kOpusDefaultStereo; extern const int kOpusDefaultUseInbandFec; +extern const int kOpusDefaultMaxPlaybackRate; + // Prefered values in this code base. Note that they may differ from the default // values in http://tools.ietf.org/html/draft-spittka-payload-rtp-opus-03 // Only frames larger or equal to 10 ms are currently supported in this code diff --git a/media/base/executablehelpers.h b/media/base/executablehelpers.h new file mode 100644 index 0000000..2dde010 --- /dev/null +++ b/media/base/executablehelpers.h @@ -0,0 +1,100 @@ +/* + * libjingle + * Copyright 2014 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TALK_MEDIA_BASE_EXECUTABLEHELPERS_H_ +#define TALK_MEDIA_BASE_EXECUTABLEHELPERS_H_ + +#ifdef OSX +#include <mach-o/dyld.h> +#endif + +#include <string> + +#include "webrtc/base/logging.h" +#include "webrtc/base/pathutils.h" + +namespace rtc { + +// Returns the path to the running executable or an empty path. +// TODO(thorcarpenter): Consolidate with FluteClient::get_executable_dir. +inline Pathname GetExecutablePath() { + const int32 kMaxExePathSize = 255; +#ifdef WIN32 + TCHAR exe_path_buffer[kMaxExePathSize]; + DWORD copied_length = GetModuleFileName(NULL, // NULL = Current process + exe_path_buffer, kMaxExePathSize); + if (0 == copied_length) { + LOG(LS_ERROR) << "Copied length is zero"; + return rtc::Pathname(); + } + if (kMaxExePathSize == copied_length) { + LOG(LS_ERROR) << "Buffer too small"; + return rtc::Pathname(); + } +#ifdef UNICODE + std::wstring wdir(exe_path_buffer); + std::string dir_tmp(wdir.begin(), wdir.end()); + rtc::Pathname path(dir_tmp); +#else // UNICODE + rtc::Pathname path(exe_path_buffer); +#endif // UNICODE +#elif defined(OSX) || defined(LINUX) + char exe_path_buffer[kMaxExePathSize]; +#ifdef OSX + uint32_t copied_length = kMaxExePathSize - 1; + if (_NSGetExecutablePath(exe_path_buffer, &copied_length) == -1) { + LOG(LS_ERROR) << "Buffer too small"; + return rtc::Pathname(); + } +#elif defined LINUX + int32 copied_length = kMaxExePathSize - 1; + const char* kProcExeFmt = "/proc/%d/exe"; + char proc_exe_link[40]; + snprintf(proc_exe_link, sizeof(proc_exe_link), kProcExeFmt, getpid()); + copied_length = readlink(proc_exe_link, exe_path_buffer, copied_length); + if (copied_length == -1) { + LOG_ERR(LS_ERROR) << "Error reading link " << proc_exe_link; + return rtc::Pathname(); + } + if (copied_length == kMaxExePathSize - 1) { + LOG(LS_ERROR) << "Probably truncated result when reading link " + << proc_exe_link; + return rtc::Pathname(); + } + exe_path_buffer[copied_length] = '\0'; +#endif // LINUX + rtc::Pathname path(exe_path_buffer); +#else // Android || IOS + rtc::Pathname path; +#endif // OSX || LINUX + return path; +} + +} // namespace rtc + +#endif // TALK_MEDIA_BASE_EXECUTABLEHELPERS_H_ + diff --git a/media/base/fakemediaengine.h b/media/base/fakemediaengine.h index 7bc3958..a6eabef 100644 --- a/media/base/fakemediaengine.h +++ b/media/base/fakemediaengine.h @@ -868,7 +868,7 @@ class FakeVoiceEngine : public FakeBaseEngine { class FakeVideoEngine : public FakeBaseEngine { public: - FakeVideoEngine() : renderer_(NULL), capture_(false), processor_(NULL) { + FakeVideoEngine() : capture_(false), processor_(NULL) { // Add a fake video codec. Note that the name must not be "" as there are // sanity checks against that. codecs_.push_back(VideoCodec(0, "fake_video_codec", 0, 0, 0, 0)); @@ -926,10 +926,6 @@ class FakeVideoEngine : public FakeBaseEngine { options_changed_ = true; return true; } - bool SetLocalRenderer(VideoRenderer* r) { - renderer_ = r; - return true; - } bool SetCapture(bool capture) { capture_ = capture; return true; @@ -946,7 +942,6 @@ class FakeVideoEngine : public FakeBaseEngine { std::vector<VideoCodec> codecs_; VideoEncoderConfig default_encoder_config_; std::string in_device_; - VideoRenderer* renderer_; bool capture_; VideoProcessor* processor_; VideoOptions options_; @@ -994,7 +989,6 @@ class FakeMediaEngine : } const std::string& audio_in_device() const { return voice_.in_device_; } const std::string& audio_out_device() const { return voice_.out_device_; } - VideoRenderer* local_renderer() { return video_.renderer_; } int voice_loglevel() const { return voice_.loglevel_; } const std::string& voice_logfilter() const { return voice_.logfilter_; } int video_loglevel() const { return video_.loglevel_; } diff --git a/media/base/filemediaengine.h b/media/base/filemediaengine.h index e546328..d3e99a8 100644 --- a/media/base/filemediaengine.h +++ b/media/base/filemediaengine.h @@ -88,7 +88,6 @@ class FileMediaEngine : public MediaEngineInterface { virtual SoundclipMedia* CreateSoundclip() { return NULL; } virtual AudioOptions GetAudioOptions() const { return AudioOptions(); } virtual bool SetAudioOptions(const AudioOptions& options) { return true; } - virtual bool SetVideoOptions(const VideoOptions& options) { return true; } virtual bool SetAudioDelayOffset(int offset) { return true; } virtual bool SetDefaultVideoEncoderConfig(const VideoEncoderConfig& config) { return true; @@ -113,7 +112,6 @@ class FileMediaEngine : public MediaEngineInterface { virtual bool SetOutputVolume(int level) { return true; } virtual int GetInputLevel() { return 0; } virtual bool SetLocalMonitor(bool enable) { return true; } - virtual bool SetLocalRenderer(VideoRenderer* renderer) { return true; } // TODO(whyuan): control channel send? virtual bool SetVideoCapture(bool capture) { return true; } virtual const std::vector<AudioCodec>& audio_codecs() { diff --git a/media/base/filemediaengine_unittest.cc b/media/base/filemediaengine_unittest.cc index 1f7405d..c542baf 100644 --- a/media/base/filemediaengine_unittest.cc +++ b/media/base/filemediaengine_unittest.cc @@ -223,8 +223,6 @@ TEST_F(FileMediaEngineTest, TestDefaultImplementation) { EXPECT_TRUE(NULL == engine_->CreateSoundclip()); cricket::AudioOptions audio_options; EXPECT_TRUE(engine_->SetAudioOptions(audio_options)); - cricket::VideoOptions video_options; - EXPECT_TRUE(engine_->SetVideoOptions(video_options)); VideoEncoderConfig video_encoder_config; EXPECT_TRUE(engine_->SetDefaultVideoEncoderConfig(video_encoder_config)); EXPECT_TRUE(engine_->SetSoundDevices(NULL, NULL)); @@ -232,7 +230,6 @@ TEST_F(FileMediaEngineTest, TestDefaultImplementation) { EXPECT_TRUE(engine_->SetOutputVolume(0)); EXPECT_EQ(0, engine_->GetInputLevel()); EXPECT_TRUE(engine_->SetLocalMonitor(true)); - EXPECT_TRUE(engine_->SetLocalRenderer(NULL)); EXPECT_TRUE(engine_->SetVideoCapture(true)); EXPECT_EQ(0U, engine_->audio_codecs().size()); EXPECT_EQ(0U, engine_->video_codecs().size()); diff --git a/media/base/hybridvideoengine.cc b/media/base/hybridvideoengine.cc deleted file mode 100644 index 289c4fe..0000000 --- a/media/base/hybridvideoengine.cc +++ /dev/null @@ -1,356 +0,0 @@ -/* - * libjingle - * Copyright 2004 Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "talk/media/base/hybridvideoengine.h" - -#include "webrtc/base/logging.h" - -namespace cricket { - -HybridVideoMediaChannel::HybridVideoMediaChannel( - HybridVideoEngineInterface* engine, - VideoMediaChannel* channel1, - VideoMediaChannel* channel2) - : engine_(engine), - channel1_(channel1), - channel2_(channel2), - active_channel_(NULL), - sending_(false) { -} - -HybridVideoMediaChannel::~HybridVideoMediaChannel() { -} - -void HybridVideoMediaChannel::SetInterface(NetworkInterface* iface) { - if (channel1_) { - channel1_->SetInterface(iface); - } - if (channel2_) { - channel2_->SetInterface(iface); - } -} - -bool HybridVideoMediaChannel::SetOptions(const VideoOptions &options) { - bool ret = true; - if (channel1_) { - ret = channel1_->SetOptions(options); - } - if (channel2_ && ret) { - ret = channel2_->SetOptions(options); - } - return ret; -} - -bool HybridVideoMediaChannel::GetOptions(VideoOptions *options) const { - if (active_channel_) { - return active_channel_->GetOptions(options); - } - if (channel1_) { - return channel1_->GetOptions(options); - } - if (channel2_) { - return channel2_->GetOptions(options); - } - return false; -} - -bool HybridVideoMediaChannel::SetRecvCodecs( - const std::vector<VideoCodec>& codecs) { - // Only give each channel the codecs it knows about. - bool ret = true; - std::vector<VideoCodec> codecs1, codecs2; - SplitCodecs(codecs, &codecs1, &codecs2); - if (channel1_) { - ret = channel1_->SetRecvCodecs(codecs1); - } - if (channel2_ && ret) { - ret = channel2_->SetRecvCodecs(codecs2); - } - return ret; -} - -bool HybridVideoMediaChannel::SetRecvRtpHeaderExtensions( - const std::vector<RtpHeaderExtension>& extensions) { - bool ret = true; - if (channel1_) { - ret = channel1_->SetRecvRtpHeaderExtensions(extensions); - } - if (channel2_ && ret) { - ret = channel2_->SetRecvRtpHeaderExtensions(extensions); - } - return ret; -} - -bool HybridVideoMediaChannel::SetRenderer(uint32 ssrc, - VideoRenderer* renderer) { - bool ret = true; - if (channel1_) { - ret = channel1_->SetRenderer(ssrc, renderer); - } - if (channel2_ && ret) { - ret = channel2_->SetRenderer(ssrc, renderer); - } - return ret; -} - -bool HybridVideoMediaChannel::SetRender(bool render) { - bool ret = true; - if (channel1_) { - ret = channel1_->SetRender(render); - } - if (channel2_ && ret) { - ret = channel2_->SetRender(render); - } - return ret; -} - -bool HybridVideoMediaChannel::MuteStream(uint32 ssrc, bool muted) { - bool ret = true; - if (channel1_) { - ret = channel1_->MuteStream(ssrc, muted); - } - if (channel2_ && ret) { - ret = channel2_->MuteStream(ssrc, muted); - } - return ret; -} - -bool HybridVideoMediaChannel::SetSendCodecs( - const std::vector<VideoCodec>& codecs) { - // Use the input to this function to decide what impl we're going to use. - if (!active_channel_ && !SelectActiveChannel(codecs)) { - LOG(LS_WARNING) << "Failed to select active channel"; - return false; - } - // Only give the active channel the codecs it knows about. - std::vector<VideoCodec> codecs1, codecs2; - SplitCodecs(codecs, &codecs1, &codecs2); - const std::vector<VideoCodec>& codecs_to_set = - (active_channel_ == channel1_.get()) ? codecs1 : codecs2; - bool return_value = active_channel_->SetSendCodecs(codecs_to_set); - if (!return_value) { - return false; - } - VideoCodec send_codec; - return_value = active_channel_->GetSendCodec(&send_codec); - if (!return_value) { - return false; - } - engine_->OnNewSendResolution(send_codec.width, send_codec.height); - active_channel_->UpdateAspectRatio(send_codec.width, send_codec.height); - return true; -} - -bool HybridVideoMediaChannel::GetSendCodec(VideoCodec* send_codec) { - if (!active_channel_) { - return false; - } - return active_channel_->GetSendCodec(send_codec); -} - -bool HybridVideoMediaChannel::SetSendStreamFormat(uint32 ssrc, - const VideoFormat& format) { - return active_channel_ && active_channel_->SetSendStreamFormat(ssrc, format); -} - -bool HybridVideoMediaChannel::SetSendRtpHeaderExtensions( - const std::vector<RtpHeaderExtension>& extensions) { - return active_channel_ && - active_channel_->SetSendRtpHeaderExtensions(extensions); -} - -bool HybridVideoMediaChannel::SetStartSendBandwidth(int bps) { - return active_channel_ && active_channel_->SetStartSendBandwidth(bps); -} - -bool HybridVideoMediaChannel::SetMaxSendBandwidth(int bps) { - return active_channel_ && active_channel_->SetMaxSendBandwidth(bps); -} - -bool HybridVideoMediaChannel::SetSend(bool send) { - if (send == sending()) { - return true; // no action required if already set. - } - - bool ret = active_channel_ && - active_channel_->SetSend(send); - - // Returns error and don't connect the signal if starting up. - // Disconnects the signal anyway if shutting down. - if (ret || !send) { - // TODO(juberti): Remove this hack that connects the WebRTC channel - // to the capturer. - if (active_channel_ == channel1_.get()) { - engine_->OnSendChange1(channel1_.get(), send); - } else { - engine_->OnSendChange2(channel2_.get(), send); - } - // If succeeded, remember the state as is. - // If failed to open, sending_ should be false. - // If failed to stop, sending_ should also be false, as we disconnect the - // capture anyway. - // The failure on SetSend(false) is a known issue in webrtc. - sending_ = send; - } - return ret; -} - -bool HybridVideoMediaChannel::SetCapturer(uint32 ssrc, - VideoCapturer* capturer) { - bool ret = true; - if (channel1_.get()) { - ret = channel1_->SetCapturer(ssrc, capturer); - } - if (channel2_.get() && ret) { - ret = channel2_->SetCapturer(ssrc, capturer); - } - return ret; -} - -bool HybridVideoMediaChannel::AddSendStream(const StreamParams& sp) { - bool ret = true; - if (channel1_) { - ret = channel1_->AddSendStream(sp); - } - if (channel2_ && ret) { - ret = channel2_->AddSendStream(sp); - } - return ret; -} - -bool HybridVideoMediaChannel::RemoveSendStream(uint32 ssrc) { - bool ret = true; - if (channel1_) { - ret = channel1_->RemoveSendStream(ssrc); - } - if (channel2_ && ret) { - ret = channel2_->RemoveSendStream(ssrc); - } - return ret; -} - -bool HybridVideoMediaChannel::AddRecvStream(const StreamParams& sp) { - return active_channel_ && - active_channel_->AddRecvStream(sp); -} - -bool HybridVideoMediaChannel::RemoveRecvStream(uint32 ssrc) { - return active_channel_ && - active_channel_->RemoveRecvStream(ssrc); -} - -bool HybridVideoMediaChannel::SendIntraFrame() { - return active_channel_ && - active_channel_->SendIntraFrame(); -} - -bool HybridVideoMediaChannel::RequestIntraFrame() { - return active_channel_ && - active_channel_->RequestIntraFrame(); -} - -bool HybridVideoMediaChannel::GetStats( - const StatsOptions& options, VideoMediaInfo* info) { - // TODO(juberti): Ensure that returning no stats until SetSendCodecs is OK. - return active_channel_ && - active_channel_->GetStats(options, info); -} - -void HybridVideoMediaChannel::OnPacketReceived( - rtc::Buffer* packet, const rtc::PacketTime& packet_time) { - // Eat packets until we have an active channel; - if (active_channel_) { - active_channel_->OnPacketReceived(packet, packet_time); - } else { - LOG(LS_INFO) << "HybridVideoChannel: Eating early RTP packet"; - } -} - -void HybridVideoMediaChannel::OnRtcpReceived( - rtc::Buffer* packet, const rtc::PacketTime& packet_time) { - // Eat packets until we have an active channel; - if (active_channel_) { - active_channel_->OnRtcpReceived(packet, packet_time); - } else { - LOG(LS_INFO) << "HybridVideoChannel: Eating early RTCP packet"; - } -} - -void HybridVideoMediaChannel::OnReadyToSend(bool ready) { - if (channel1_) { - channel1_->OnReadyToSend(ready); - } - if (channel2_) { - channel2_->OnReadyToSend(ready); - } -} - -void HybridVideoMediaChannel::UpdateAspectRatio(int ratio_w, int ratio_h) { - if (active_channel_) active_channel_->UpdateAspectRatio(ratio_w, ratio_h); -} - -bool HybridVideoMediaChannel::SelectActiveChannel( - const std::vector<VideoCodec>& codecs) { - if (!active_channel_ && !codecs.empty()) { - if (engine_->HasCodec1(codecs[0])) { - channel2_.reset(); - active_channel_ = channel1_.get(); - } else if (engine_->HasCodec2(codecs[0])) { - channel1_.reset(); - active_channel_ = channel2_.get(); - } - } - if (NULL == active_channel_) { - return false; - } - // Connect signals from the active channel. - active_channel_->SignalMediaError.connect( - this, - &HybridVideoMediaChannel::OnMediaError); - return true; -} - -void HybridVideoMediaChannel::SplitCodecs( - const std::vector<VideoCodec>& codecs, - std::vector<VideoCodec>* codecs1, std::vector<VideoCodec>* codecs2) { - codecs1->clear(); - codecs2->clear(); - for (size_t i = 0; i < codecs.size(); ++i) { - if (engine_->HasCodec1(codecs[i])) { - codecs1->push_back(codecs[i]); - } - if (engine_->HasCodec2(codecs[i])) { - codecs2->push_back(codecs[i]); - } - } -} - -void HybridVideoMediaChannel::OnMediaError(uint32 ssrc, Error error) { - SignalMediaError(ssrc, error); -} - -} // namespace cricket diff --git a/media/base/hybridvideoengine.h b/media/base/hybridvideoengine.h deleted file mode 100644 index 004d3cf..0000000 --- a/media/base/hybridvideoengine.h +++ /dev/null @@ -1,286 +0,0 @@ -/* - * libjingle - * Copyright 2004 Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef TALK_MEDIA_BASE_HYBRIDVIDEOENGINE_H_ -#define TALK_MEDIA_BASE_HYBRIDVIDEOENGINE_H_ - -#include <string> -#include <vector> - -#include "talk/media/base/codec.h" -#include "talk/media/base/mediachannel.h" -#include "talk/media/base/videocapturer.h" -#include "talk/media/base/videocommon.h" -#include "webrtc/base/logging.h" -#include "webrtc/base/sigslotrepeater.h" - -namespace cricket { - -struct Device; -struct VideoFormat; -class HybridVideoEngineInterface; -class VideoCapturer; -class VideoFrame; -class VideoRenderer; - -// HybridVideoMediaChannels work with a HybridVideoEngine to combine -// two unrelated VideoMediaChannel implementations into a single class. -class HybridVideoMediaChannel : public VideoMediaChannel { - public: - HybridVideoMediaChannel(HybridVideoEngineInterface* engine, - VideoMediaChannel* channel1, - VideoMediaChannel* channel2); - virtual ~HybridVideoMediaChannel(); - - // VideoMediaChannel methods - virtual void SetInterface(NetworkInterface* iface); - virtual bool SetOptions(const VideoOptions& options); - virtual bool GetOptions(VideoOptions* options) const; - virtual bool AddSendStream(const StreamParams& sp); - virtual bool RemoveSendStream(uint32 ssrc); - virtual bool SetRenderer(uint32 ssrc, VideoRenderer* renderer); - virtual bool SetRender(bool render); - virtual bool MuteStream(uint32 ssrc, bool muted); - - virtual bool SetRecvCodecs(const std::vector<VideoCodec>& codecs); - virtual bool SetRecvRtpHeaderExtensions( - const std::vector<RtpHeaderExtension>& extensions); - - virtual bool SetSendCodecs(const std::vector<VideoCodec>& codecs); - virtual bool GetSendCodec(VideoCodec* codec); - virtual bool SetSendStreamFormat(uint32 ssrc, const VideoFormat& format); - virtual bool SetSendRtpHeaderExtensions( - const std::vector<RtpHeaderExtension>& extensions); - virtual bool SetStartSendBandwidth(int bps); - virtual bool SetMaxSendBandwidth(int bps); - virtual bool SetSend(bool send); - - virtual bool AddRecvStream(const StreamParams& sp); - virtual bool RemoveRecvStream(uint32 ssrc); - virtual bool SetCapturer(uint32 ssrc, VideoCapturer* capturer); - - virtual bool SendIntraFrame(); - virtual bool RequestIntraFrame(); - - virtual bool GetStats(const StatsOptions& options, VideoMediaInfo* info); - - virtual void OnPacketReceived(rtc::Buffer* packet, - const rtc::PacketTime& packet_time); - virtual void OnRtcpReceived(rtc::Buffer* packet, - const rtc::PacketTime& packet_time); - virtual void OnReadyToSend(bool ready); - - virtual void UpdateAspectRatio(int ratio_w, int ratio_h); - - void OnLocalFrame(VideoCapturer*, const VideoFrame*); - void OnLocalFrameFormat(VideoCapturer*, const VideoFormat*); - - bool sending() const { return sending_; } - - private: - bool SelectActiveChannel(const std::vector<VideoCodec>& codecs); - void SplitCodecs(const std::vector<VideoCodec>& codecs, - std::vector<VideoCodec>* codecs1, - std::vector<VideoCodec>* codecs2); - - void OnMediaError(uint32 ssrc, Error error); - - HybridVideoEngineInterface* engine_; - rtc::scoped_ptr<VideoMediaChannel> channel1_; - rtc::scoped_ptr<VideoMediaChannel> channel2_; - VideoMediaChannel* active_channel_; - bool sending_; -}; - -// Interface class for HybridVideoChannels to talk to the engine. -class HybridVideoEngineInterface { - public: - virtual ~HybridVideoEngineInterface() {} - virtual bool HasCodec1(const VideoCodec& codec) = 0; - virtual bool HasCodec2(const VideoCodec& codec) = 0; - virtual void OnSendChange1(VideoMediaChannel* channel1, bool send) = 0; - virtual void OnSendChange2(VideoMediaChannel* channel1, bool send) = 0; - virtual void OnNewSendResolution(int width, int height) = 0; -}; - -// The HybridVideoEngine class combines two unrelated VideoEngine impls -// into a single class. It creates HybridVideoMediaChannels that also contain -// a VideoMediaChannel implementation from each engine. Policy is then used -// during call setup to determine which VideoMediaChannel should be used. -// Currently, this policy is based on what codec the remote side wants to use. -template<class VIDEO1, class VIDEO2> -class HybridVideoEngine : public HybridVideoEngineInterface { - public: - HybridVideoEngine() { - // Unify the codec lists. - codecs_ = video1_.codecs(); - codecs_.insert(codecs_.end(), video2_.codecs().begin(), - video2_.codecs().end()); - - rtp_header_extensions_ = video1_.rtp_header_extensions(); - rtp_header_extensions_.insert(rtp_header_extensions_.end(), - video2_.rtp_header_extensions().begin(), - video2_.rtp_header_extensions().end()); - - SignalCaptureStateChange.repeat(video2_.SignalCaptureStateChange); - } - - bool Init(rtc::Thread* worker_thread) { - if (!video1_.Init(worker_thread)) { - LOG(LS_ERROR) << "Failed to init VideoEngine1"; - return false; - } - if (!video2_.Init(worker_thread)) { - LOG(LS_ERROR) << "Failed to init VideoEngine2"; - video1_.Terminate(); - return false; - } - return true; - } - void Terminate() { - video1_.Terminate(); - video2_.Terminate(); - } - - int GetCapabilities() { - return (video1_.GetCapabilities() | video2_.GetCapabilities()); - } - HybridVideoMediaChannel* CreateChannel(VoiceMediaChannel* channel) { - rtc::scoped_ptr<VideoMediaChannel> channel1( - video1_.CreateChannel(channel)); - if (!channel1) { - LOG(LS_ERROR) << "Failed to create VideoMediaChannel1"; - return NULL; - } - rtc::scoped_ptr<VideoMediaChannel> channel2( - video2_.CreateChannel(channel)); - if (!channel2) { - LOG(LS_ERROR) << "Failed to create VideoMediaChannel2"; - return NULL; - } - return new HybridVideoMediaChannel(this, - channel1.release(), channel2.release()); - } - - bool SetOptions(const VideoOptions& options) { - return video1_.SetOptions(options) && video2_.SetOptions(options); - } - bool SetDefaultEncoderConfig(const VideoEncoderConfig& config) { - VideoEncoderConfig conf = config; - if (video1_.codecs().size() > 0) { - conf.max_codec.name = video1_.codecs()[0].name; - if (!video1_.SetDefaultEncoderConfig(conf)) { - LOG(LS_ERROR) << "Failed to SetDefaultEncoderConfig for video1"; - return false; - } - } - if (video2_.codecs().size() > 0) { - conf.max_codec.name = video2_.codecs()[0].name; - if (!video2_.SetDefaultEncoderConfig(conf)) { - LOG(LS_ERROR) << "Failed to SetDefaultEncoderConfig for video2"; - return false; - } - } - return true; - } - VideoEncoderConfig GetDefaultEncoderConfig() const { - // This looks pretty strange, but, in practice, it'll do sane things if - // GetDefaultEncoderConfig is only called after SetDefaultEncoderConfig, - // since both engines should be essentially equivalent at that point. If it - // hasn't been called, though, we'll use the first meaningful encoder - // config, or the config from the second video engine if neither are - // meaningful. - VideoEncoderConfig config = video1_.GetDefaultEncoderConfig(); - if (config.max_codec.width != 0) { - return config; - } else { - return video2_.GetDefaultEncoderConfig(); - } - } - const std::vector<VideoCodec>& codecs() const { - return codecs_; - } - const std::vector<RtpHeaderExtension>& rtp_header_extensions() const { - return rtp_header_extensions_; - } - void SetLogging(int min_sev, const char* filter) { - video1_.SetLogging(min_sev, filter); - video2_.SetLogging(min_sev, filter); - } - - VideoFormat GetStartCaptureFormat() const { - return video2_.GetStartCaptureFormat(); - } - - // TODO(juberti): Remove these functions after we do the capturer refactoring. - // For now they are set to always use the second engine for capturing, which - // is convenient given our intended use case. - bool SetCaptureDevice(const Device* device) { - return video2_.SetCaptureDevice(device); - } - VideoCapturer* GetVideoCapturer() const { - return video2_.GetVideoCapturer(); - } - bool SetLocalRenderer(VideoRenderer* renderer) { - return video2_.SetLocalRenderer(renderer); - } - sigslot::repeater2<VideoCapturer*, CaptureState> SignalCaptureStateChange; - - virtual bool HasCodec1(const VideoCodec& codec) { - return HasCodec(video1_, codec); - } - virtual bool HasCodec2(const VideoCodec& codec) { - return HasCodec(video2_, codec); - } - template<typename VIDEO> - bool HasCodec(const VIDEO& engine, const VideoCodec& codec) const { - for (std::vector<VideoCodec>::const_iterator i = engine.codecs().begin(); - i != engine.codecs().end(); - ++i) { - if (i->Matches(codec)) { - return true; - } - } - return false; - } - virtual void OnSendChange1(VideoMediaChannel* channel1, bool send) { - } - virtual void OnSendChange2(VideoMediaChannel* channel2, bool send) { - } - virtual void OnNewSendResolution(int width, int height) { - } - - protected: - VIDEO1 video1_; - VIDEO2 video2_; - std::vector<VideoCodec> codecs_; - std::vector<RtpHeaderExtension> rtp_header_extensions_; -}; - -} // namespace cricket - -#endif // TALK_MEDIA_BASE_HYBRIDVIDEOENGINE_H_ diff --git a/media/base/hybridvideoengine_unittest.cc b/media/base/hybridvideoengine_unittest.cc deleted file mode 100644 index 7b409ea..0000000 --- a/media/base/hybridvideoengine_unittest.cc +++ /dev/null @@ -1,486 +0,0 @@ -/* - * libjingle - * Copyright 2004 Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "talk/media/base/fakemediaengine.h" -#include "talk/media/base/fakenetworkinterface.h" -#include "talk/media/base/fakevideocapturer.h" -#include "talk/media/base/hybridvideoengine.h" -#include "talk/media/base/mediachannel.h" -#include "talk/media/base/testutils.h" -#include "webrtc/base/gunit.h" - -static const cricket::VideoCodec kGenericCodec(97, "Generic", 640, 360, 30, 0); -static const cricket::VideoCodec kVp8Codec(100, "VP8", 640, 360, 30, 0); -static const cricket::VideoCodec kCodecsVp8Only[] = { kVp8Codec }; -static const cricket::VideoCodec kCodecsGenericOnly[] = { kGenericCodec }; -static const cricket::VideoCodec kCodecsVp8First[] = { kVp8Codec, - kGenericCodec }; -static const cricket::VideoCodec kCodecsGenericFirst[] = { kGenericCodec, - kVp8Codec }; - -using cricket::StreamParams; - -class FakeVp8VideoEngine : public cricket::FakeVideoEngine { - public: - FakeVp8VideoEngine() { - SetCodecs(MAKE_VECTOR(kCodecsVp8Only)); - } -}; -class FakeGenericVideoEngine : public cricket::FakeVideoEngine { - public: - FakeGenericVideoEngine() { - SetCodecs(MAKE_VECTOR(kCodecsGenericOnly)); - } - - // For testing purposes, mimic the behavior of a media engine that throws out - // resolutions that don't match the codec list. A width or height of 0 - // trivially will never match the codec list, so this is sufficient for - // testing the case we want (0x0). - virtual bool FindCodec(const cricket::VideoCodec& codec) { - if (codec.width == 0 || codec.height == 0) { - return false; - } else { - return cricket::FakeVideoEngine::FindCodec(codec); - } - } -}; -class HybridVideoEngineForTest : public cricket::HybridVideoEngine< - FakeVp8VideoEngine, FakeGenericVideoEngine> { - public: - HybridVideoEngineForTest() - : - num_ch1_send_on_(0), - num_ch1_send_off_(0), - send_width_(0), - send_height_(0) { } - cricket::FakeVideoEngine* sub_engine1() { return &video1_; } - cricket::FakeVideoEngine* sub_engine2() { return &video2_; } - - // From base class HybridVideoEngine. - void OnSendChange1(cricket::VideoMediaChannel* channel1, bool send) { - if (send) { - ++num_ch1_send_on_; - } else { - ++num_ch1_send_off_; - } - } - // From base class HybridVideoEngine - void OnNewSendResolution(int width, int height) { - send_width_ = width; - send_height_ = height; - } - - int num_ch1_send_on() const { return num_ch1_send_on_; } - int num_ch1_send_off() const { return num_ch1_send_off_; } - - int send_width() const { return send_width_; } - int send_height() const { return send_height_; } - - private: - int num_ch1_send_on_; - int num_ch1_send_off_; - - int send_width_; - int send_height_; -}; - -class HybridVideoEngineTest : public testing::Test { - public: - HybridVideoEngineTest() : sub_channel1_(NULL), sub_channel2_(NULL) { - } - ~HybridVideoEngineTest() { - engine_.Terminate(); - } - bool SetupEngine() { - bool result = engine_.Init(rtc::Thread::Current()); - if (result) { - channel_.reset(engine_.CreateChannel(NULL)); - result = (channel_.get() != NULL); - sub_channel1_ = engine_.sub_engine1()->GetChannel(0); - sub_channel2_ = engine_.sub_engine2()->GetChannel(0); - } - return result; - } - bool SetupRenderAndAddStream(const StreamParams& sp) { - if (!SetupEngine()) - return false; - channel_->SetInterface(transport_.get()); - return channel_->SetRecvCodecs(engine_.codecs()) && - channel_->AddSendStream(sp) && - channel_->SetRender(true); - } - void DeliverPacket(const void* data, int len) { - rtc::Buffer packet(data, len); - channel_->OnPacketReceived(&packet, rtc::CreatePacketTime(0)); - } - void DeliverRtcp(const void* data, int len) { - rtc::Buffer packet(data, len); - channel_->OnRtcpReceived(&packet, rtc::CreatePacketTime(0)); - } - - protected: - void TestSetSendCodecs(cricket::FakeVideoEngine* sub_engine, - const std::vector<cricket::VideoCodec>& codecs) { - EXPECT_TRUE(SetupRenderAndAddStream(StreamParams::CreateLegacy(1234))); - EXPECT_TRUE(channel_->SetSendCodecs(codecs)); - cricket::FakeVideoMediaChannel* sub_channel = sub_engine->GetChannel(0); - ASSERT_EQ(1U, sub_channel->send_codecs().size()); - EXPECT_EQ(codecs[0], sub_channel->send_codecs()[0]); - EXPECT_TRUE(channel_->SetSend(true)); - EXPECT_TRUE(sub_channel->sending()); - } - void TestSetSendBandwidth(cricket::FakeVideoEngine* sub_engine, - const std::vector<cricket::VideoCodec>& codecs, - int start_bitrate, - int max_bitrate) { - EXPECT_TRUE(SetupRenderAndAddStream(StreamParams::CreateLegacy(1234))); - EXPECT_TRUE(channel_->SetSendCodecs(codecs)); - EXPECT_TRUE(channel_->SetStartSendBandwidth(start_bitrate)); - EXPECT_TRUE(channel_->SetMaxSendBandwidth(max_bitrate)); - cricket::FakeVideoMediaChannel* sub_channel = sub_engine->GetChannel(0); - EXPECT_EQ(start_bitrate, sub_channel->start_bps()); - EXPECT_EQ(max_bitrate, sub_channel->max_bps()); - } - HybridVideoEngineForTest engine_; - rtc::scoped_ptr<cricket::HybridVideoMediaChannel> channel_; - rtc::scoped_ptr<cricket::FakeNetworkInterface> transport_; - cricket::FakeVideoMediaChannel* sub_channel1_; - cricket::FakeVideoMediaChannel* sub_channel2_; -}; - -TEST_F(HybridVideoEngineTest, StartupShutdown) { - EXPECT_TRUE(engine_.Init(rtc::Thread::Current())); - engine_.Terminate(); -} - -// Tests that SetDefaultVideoEncoderConfig passes down to both engines. -TEST_F(HybridVideoEngineTest, SetDefaultVideoEncoderConfig) { - cricket::VideoEncoderConfig config( - cricket::VideoCodec(105, "", 640, 400, 30, 0), 1, 2); - EXPECT_TRUE(engine_.SetDefaultEncoderConfig(config)); - - cricket::VideoEncoderConfig config_1 = config; - config_1.max_codec.name = kCodecsVp8Only[0].name; - EXPECT_EQ(config_1, engine_.sub_engine1()->default_encoder_config()); - - cricket::VideoEncoderConfig config_2 = config; - config_2.max_codec.name = kCodecsGenericOnly[0].name; - EXPECT_EQ(config_2, engine_.sub_engine2()->default_encoder_config()); -} - -// Tests that GetDefaultVideoEncoderConfig picks a meaningful encoder config -// based on the underlying engine config and then after a call to -// SetDefaultEncoderConfig on the hybrid engine. -TEST_F(HybridVideoEngineTest, SetDefaultVideoEncoderConfigDefaultValue) { - cricket::VideoEncoderConfig blank_config; - cricket::VideoEncoderConfig meaningful_config1( - cricket::VideoCodec(111, "abcd", 320, 240, 30, 0), 1, 2); - cricket::VideoEncoderConfig meaningful_config2( - cricket::VideoCodec(111, "abcd", 1280, 720, 30, 0), 1, 2); - cricket::VideoEncoderConfig meaningful_config3( - cricket::VideoCodec(111, "abcd", 640, 360, 30, 0), 1, 2); - engine_.sub_engine1()->SetDefaultEncoderConfig(blank_config); - engine_.sub_engine2()->SetDefaultEncoderConfig(blank_config); - EXPECT_EQ(blank_config, engine_.GetDefaultEncoderConfig()); - - engine_.sub_engine2()->SetDefaultEncoderConfig(meaningful_config2); - EXPECT_EQ(meaningful_config2, engine_.GetDefaultEncoderConfig()); - - engine_.sub_engine1()->SetDefaultEncoderConfig(meaningful_config1); - EXPECT_EQ(meaningful_config1, engine_.GetDefaultEncoderConfig()); - - EXPECT_TRUE(engine_.SetDefaultEncoderConfig(meaningful_config3)); - // The overall config should now match, though the codec name will have been - // rewritten for the first media engine. - meaningful_config3.max_codec.name = kCodecsVp8Only[0].name; - EXPECT_EQ(meaningful_config3, engine_.GetDefaultEncoderConfig()); -} - -// Tests that our engine has the right codecs in the right order. -TEST_F(HybridVideoEngineTest, CheckCodecs) { - const std::vector<cricket::VideoCodec>& c = engine_.codecs(); - ASSERT_EQ(2U, c.size()); - EXPECT_EQ(kVp8Codec, c[0]); - EXPECT_EQ(kGenericCodec, c[1]); -} - -// Tests that our engine has the right caps. -TEST_F(HybridVideoEngineTest, CheckCaps) { - EXPECT_EQ(cricket::VIDEO_SEND | cricket::VIDEO_RECV, - engine_.GetCapabilities()); -} - -// Tests that we can create and destroy a channel. -TEST_F(HybridVideoEngineTest, CreateChannel) { - EXPECT_TRUE(SetupEngine()); - EXPECT_TRUE(sub_channel1_ != NULL); - EXPECT_TRUE(sub_channel2_ != NULL); -} - -// Tests that we properly handle failures in CreateChannel. -TEST_F(HybridVideoEngineTest, CreateChannelFail) { - engine_.sub_engine1()->set_fail_create_channel(true); - EXPECT_FALSE(SetupEngine()); - EXPECT_TRUE(channel_.get() == NULL); - EXPECT_TRUE(sub_channel1_ == NULL); - EXPECT_TRUE(sub_channel2_ == NULL); - engine_.sub_engine1()->set_fail_create_channel(false); - engine_.sub_engine2()->set_fail_create_channel(true); - EXPECT_FALSE(SetupEngine()); - EXPECT_TRUE(channel_.get() == NULL); - EXPECT_TRUE(sub_channel1_ == NULL); - EXPECT_TRUE(sub_channel2_ == NULL); -} - -// Test that we set our inbound codecs and settings properly. -TEST_F(HybridVideoEngineTest, SetLocalDescription) { - EXPECT_TRUE(SetupEngine()); - channel_->SetInterface(transport_.get()); - EXPECT_TRUE(channel_->SetRecvCodecs(engine_.codecs())); - ASSERT_EQ(1U, sub_channel1_->recv_codecs().size()); - ASSERT_EQ(1U, sub_channel2_->recv_codecs().size()); - EXPECT_EQ(kVp8Codec, sub_channel1_->recv_codecs()[0]); - EXPECT_EQ(kGenericCodec, sub_channel2_->recv_codecs()[0]); - StreamParams stream; - stream.id = "TestStream"; - stream.ssrcs.push_back(1234); - stream.cname = "5678"; - EXPECT_TRUE(channel_->AddSendStream(stream)); - EXPECT_EQ(1234U, sub_channel1_->send_ssrc()); - EXPECT_EQ(1234U, sub_channel2_->send_ssrc()); - EXPECT_EQ("5678", sub_channel1_->rtcp_cname()); - EXPECT_EQ("5678", sub_channel2_->rtcp_cname()); - EXPECT_TRUE(channel_->SetRender(true)); - // We've called SetRender, so we should be playing out, but not yet sending. - EXPECT_TRUE(sub_channel1_->playout()); - EXPECT_TRUE(sub_channel2_->playout()); - EXPECT_FALSE(sub_channel1_->sending()); - EXPECT_FALSE(sub_channel2_->sending()); - // We may get SetSend(false) calls during call setup. - // Since this causes no change in state, they should no-op and return true. - EXPECT_TRUE(channel_->SetSend(false)); - EXPECT_FALSE(sub_channel1_->sending()); - EXPECT_FALSE(sub_channel2_->sending()); -} - -TEST_F(HybridVideoEngineTest, OnNewSendResolution) { - EXPECT_TRUE(SetupEngine()); - EXPECT_TRUE(channel_->SetSendCodecs(MAKE_VECTOR(kCodecsVp8First))); - EXPECT_EQ(640, engine_.send_width()); - EXPECT_EQ(360, engine_.send_height()); -} - -// Test that we converge to the active channel for engine 1. -TEST_F(HybridVideoEngineTest, SetSendCodecs1) { - // This will nuke the object that sub_channel2_ points to. - TestSetSendCodecs(engine_.sub_engine1(), MAKE_VECTOR(kCodecsVp8First)); - EXPECT_TRUE(engine_.sub_engine2()->GetChannel(0) == NULL); -} - -// Test that we converge to the active channel for engine 2. -TEST_F(HybridVideoEngineTest, SetSendCodecs2) { - // This will nuke the object that sub_channel1_ points to. - TestSetSendCodecs(engine_.sub_engine2(), MAKE_VECTOR(kCodecsGenericFirst)); - EXPECT_TRUE(engine_.sub_engine1()->GetChannel(0) == NULL); -} - -// Test that we don't accidentally eat 0x0 in SetSendCodecs -TEST_F(HybridVideoEngineTest, SetSendCodecs0x0) { - EXPECT_TRUE(SetupRenderAndAddStream(StreamParams::CreateLegacy(1234))); - // Send using generic codec, but with 0x0 resolution. - std::vector<cricket::VideoCodec> codecs(MAKE_VECTOR(kCodecsGenericFirst)); - codecs.resize(1); - codecs[0].width = 0; - codecs[0].height = 0; - EXPECT_TRUE(channel_->SetSendCodecs(codecs)); -} - -// Test setting the send bandwidth for VP8. -TEST_F(HybridVideoEngineTest, SetSendBandwidth1) { - TestSetSendBandwidth(engine_.sub_engine1(), - MAKE_VECTOR(kCodecsVp8First), - 100000, - 384000); -} - -// Test setting the send bandwidth for a generic codec. -TEST_F(HybridVideoEngineTest, SetSendBandwidth2) { - TestSetSendBandwidth(engine_.sub_engine2(), - MAKE_VECTOR(kCodecsGenericFirst), - 100001, - 384002); -} - -// Test that we dump RTP packets that arrive early. -TEST_F(HybridVideoEngineTest, HandleEarlyRtp) { - static const uint8 kPacket[1024] = { 0 }; - static const uint8 kRtcp[1024] = { 1 }; - EXPECT_TRUE(SetupRenderAndAddStream(StreamParams::CreateLegacy(1234))); - DeliverPacket(kPacket, sizeof(kPacket)); - DeliverRtcp(kRtcp, sizeof(kRtcp)); - EXPECT_TRUE(sub_channel1_->CheckNoRtp()); - EXPECT_TRUE(sub_channel2_->CheckNoRtp()); - EXPECT_TRUE(sub_channel1_->CheckNoRtcp()); - EXPECT_TRUE(sub_channel2_->CheckNoRtcp()); -} - -// Test that we properly pass on normal RTP packets. -TEST_F(HybridVideoEngineTest, HandleRtp) { - static const uint8 kPacket[1024] = { 0 }; - static const uint8 kRtcp[1024] = { 1 }; - EXPECT_TRUE(SetupRenderAndAddStream(StreamParams::CreateLegacy(1234))); - EXPECT_TRUE(channel_->SetSendCodecs(MAKE_VECTOR(kCodecsVp8First))); - EXPECT_TRUE(channel_->SetSend(true)); - DeliverPacket(kPacket, sizeof(kPacket)); - DeliverRtcp(kRtcp, sizeof(kRtcp)); - EXPECT_TRUE(sub_channel1_->CheckRtp(kPacket, sizeof(kPacket))); - EXPECT_TRUE(sub_channel1_->CheckRtcp(kRtcp, sizeof(kRtcp))); -} - -// Test that we properly connect media error signal. -TEST_F(HybridVideoEngineTest, MediaErrorSignal) { - cricket::VideoMediaErrorCatcher catcher; - - // Verify no signal from either channel before the active channel is set. - EXPECT_TRUE(SetupEngine()); - channel_->SignalMediaError.connect(&catcher, - &cricket::VideoMediaErrorCatcher::OnError); - sub_channel1_->SignalMediaError(1, cricket::VideoMediaChannel::ERROR_OTHER); - EXPECT_EQ(0U, catcher.ssrc()); - sub_channel2_->SignalMediaError(2, - cricket::VideoMediaChannel::ERROR_REC_DEVICE_OPEN_FAILED); - EXPECT_EQ(0U, catcher.ssrc()); - - // Set vp8 as active channel and verify that a signal comes from it. - EXPECT_TRUE(channel_->SetSendCodecs(MAKE_VECTOR(kCodecsVp8First))); - sub_channel1_->SignalMediaError(1, cricket::VideoMediaChannel::ERROR_OTHER); - EXPECT_EQ(cricket::VideoMediaChannel::ERROR_OTHER, catcher.error()); - EXPECT_EQ(1U, catcher.ssrc()); - - // Set generic codec as active channel and verify that a signal comes from it. - EXPECT_TRUE(SetupEngine()); - channel_->SignalMediaError.connect(&catcher, - &cricket::VideoMediaErrorCatcher::OnError); - EXPECT_TRUE(channel_->SetSendCodecs(MAKE_VECTOR(kCodecsGenericFirst))); - sub_channel2_->SignalMediaError(2, - cricket::VideoMediaChannel::ERROR_REC_DEVICE_OPEN_FAILED); - EXPECT_EQ(cricket::VideoMediaChannel::ERROR_REC_DEVICE_OPEN_FAILED, - catcher.error()); - EXPECT_EQ(2U, catcher.ssrc()); -} - -// Test that SetSend doesn't re-enter. -TEST_F(HybridVideoEngineTest, RepeatSetSend) { - EXPECT_TRUE(SetupEngine()); - EXPECT_TRUE(channel_->SetSendCodecs(MAKE_VECTOR(kCodecsVp8First))); - - // Verify initial status. - EXPECT_FALSE(channel_->sending()); - EXPECT_FALSE(sub_channel1_->sending()); - EXPECT_EQ(0, engine_.num_ch1_send_on()); - EXPECT_EQ(0, engine_.num_ch1_send_off()); - - // Verfiy SetSend(true) works correctly. - EXPECT_TRUE(channel_->SetSend(true)); - EXPECT_TRUE(channel_->sending()); - EXPECT_TRUE(sub_channel1_->sending()); - EXPECT_EQ(1, engine_.num_ch1_send_on()); - EXPECT_EQ(0, engine_.num_ch1_send_off()); - - // SetSend(true) again and verify nothing changes. - EXPECT_TRUE(channel_->SetSend(true)); - EXPECT_TRUE(channel_->sending()); - EXPECT_TRUE(sub_channel1_->sending()); - EXPECT_EQ(1, engine_.num_ch1_send_on()); - EXPECT_EQ(0, engine_.num_ch1_send_off()); - - // Verify SetSend(false) works correctly. - EXPECT_TRUE(channel_->SetSend(false)); - EXPECT_FALSE(channel_->sending()); - EXPECT_FALSE(sub_channel1_->sending()); - EXPECT_EQ(1, engine_.num_ch1_send_on()); - EXPECT_EQ(1, engine_.num_ch1_send_off()); - - // SetSend(false) again and verfiy nothing changes. - EXPECT_TRUE(channel_->SetSend(false)); - EXPECT_FALSE(channel_->sending()); - EXPECT_FALSE(sub_channel1_->sending()); - EXPECT_EQ(1, engine_.num_ch1_send_on()); - EXPECT_EQ(1, engine_.num_ch1_send_off()); -} - -// Test that SetOptions. -TEST_F(HybridVideoEngineTest, SetOptions) { - cricket::VideoOptions vmo; - vmo.video_high_bitrate.Set(true); - vmo.system_low_adaptation_threshhold.Set(0.10f); - EXPECT_TRUE(SetupEngine()); - EXPECT_TRUE(channel_->SetOptions(vmo)); - - bool high_bitrate; - float low; - EXPECT_TRUE(sub_channel1_->GetOptions(&vmo)); - EXPECT_TRUE(vmo.video_high_bitrate.Get(&high_bitrate)); - EXPECT_TRUE(high_bitrate); - EXPECT_TRUE(vmo.system_low_adaptation_threshhold.Get(&low)); - EXPECT_EQ(0.10f, low); - EXPECT_TRUE(sub_channel2_->GetOptions(&vmo)); - EXPECT_TRUE(vmo.video_high_bitrate.Get(&high_bitrate)); - EXPECT_TRUE(high_bitrate); - EXPECT_TRUE(vmo.system_low_adaptation_threshhold.Get(&low)); - EXPECT_EQ(0.10f, low); - - vmo.video_high_bitrate.Set(false); - vmo.system_low_adaptation_threshhold.Set(0.50f); - - EXPECT_TRUE(channel_->SetOptions(vmo)); - EXPECT_TRUE(sub_channel1_->GetOptions(&vmo)); - EXPECT_TRUE(vmo.video_high_bitrate.Get(&high_bitrate)); - EXPECT_FALSE(high_bitrate); - EXPECT_TRUE(vmo.system_low_adaptation_threshhold.Get(&low)); - EXPECT_EQ(0.50f, low); - EXPECT_TRUE(sub_channel2_->GetOptions(&vmo)); - EXPECT_TRUE(vmo.video_high_bitrate.Get(&high_bitrate)); - EXPECT_FALSE(high_bitrate); - EXPECT_TRUE(vmo.system_low_adaptation_threshhold.Get(&low)); - EXPECT_EQ(0.50f, low); -} - -TEST_F(HybridVideoEngineTest, SetCapturer) { - EXPECT_TRUE(SetupEngine()); - // Set vp8 as active channel and verify that capturer can be set. - EXPECT_TRUE(channel_->SetSendCodecs(MAKE_VECTOR(kCodecsVp8First))); - cricket::FakeVideoCapturer fake_video_capturer; - EXPECT_TRUE(channel_->SetCapturer(0, &fake_video_capturer)); - EXPECT_TRUE(channel_->SetCapturer(0, NULL)); - - // Set generic codec active channel and verify that capturer can be set. - EXPECT_TRUE(SetupEngine()); - EXPECT_TRUE(channel_->SetSendCodecs(MAKE_VECTOR(kCodecsGenericFirst))); - EXPECT_TRUE(channel_->SetCapturer(0, &fake_video_capturer)); - EXPECT_TRUE(channel_->SetCapturer(0, NULL)); -} diff --git a/media/base/mediachannel.h b/media/base/mediachannel.h index 62d6b61..5232e5d 100644 --- a/media/base/mediachannel.h +++ b/media/base/mediachannel.h @@ -182,6 +182,7 @@ struct AudioOptions { recording_sample_rate.SetFrom(change.recording_sample_rate); playout_sample_rate.SetFrom(change.playout_sample_rate); dscp.SetFrom(change.dscp); + combined_audio_video_bwe.SetFrom(change.combined_audio_video_bwe); } bool operator==(const AudioOptions& o) const { @@ -207,7 +208,8 @@ struct AudioOptions { rx_agc_limiter == o.rx_agc_limiter && recording_sample_rate == o.recording_sample_rate && playout_sample_rate == o.playout_sample_rate && - dscp == o.dscp; + dscp == o.dscp && + combined_audio_video_bwe == o.combined_audio_video_bwe; } std::string ToString() const { @@ -238,6 +240,7 @@ struct AudioOptions { ost << ToStringIfSet("recording_sample_rate", recording_sample_rate); ost << ToStringIfSet("playout_sample_rate", playout_sample_rate); ost << ToStringIfSet("dscp", dscp); + ost << ToStringIfSet("combined_audio_video_bwe", combined_audio_video_bwe); ost << "}"; return ost.str(); } @@ -275,6 +278,8 @@ struct AudioOptions { Settable<uint32> playout_sample_rate; // Set DSCP value for packet sent from audio channel. Settable<bool> dscp; + // Enable combined audio+bandwidth BWE. + Settable<bool> combined_audio_video_bwe; }; // Options that can be applied to a VideoMediaChannel or a VideoMediaEngine. diff --git a/media/base/mediaengine.h b/media/base/mediaengine.h index 6c4b740..f30e3b1 100644 --- a/media/base/mediaengine.h +++ b/media/base/mediaengine.h @@ -91,8 +91,6 @@ class MediaEngineInterface { virtual AudioOptions GetAudioOptions() const = 0; // Sets global audio options. "options" are from AudioOptions, above. virtual bool SetAudioOptions(const AudioOptions& options) = 0; - // Sets global video options. "options" are from VideoOptions, above. - virtual bool SetVideoOptions(const VideoOptions& options) = 0; // Sets the value used by the echo canceller to offset delay values obtained // from the OS. virtual bool SetAudioDelayOffset(int offset) = 0; @@ -124,7 +122,6 @@ class MediaEngineInterface { // when a VoiceMediaChannel starts sending. virtual bool SetLocalMonitor(bool enable) = 0; // Installs a callback for raw frames from the local camera. - virtual bool SetLocalRenderer(VideoRenderer* renderer) = 0; virtual const std::vector<AudioCodec>& audio_codecs() = 0; virtual const std::vector<RtpHeaderExtension>& @@ -214,9 +211,6 @@ class CompositeMediaEngine : public MediaEngineInterface { virtual bool SetAudioOptions(const AudioOptions& options) { return voice_.SetOptions(options); } - virtual bool SetVideoOptions(const VideoOptions& options) { - return video_.SetOptions(options); - } virtual bool SetAudioDelayOffset(int offset) { return voice_.SetDelayOffset(offset); } @@ -245,10 +239,6 @@ class CompositeMediaEngine : public MediaEngineInterface { virtual bool SetLocalMonitor(bool enable) { return voice_.SetLocalMonitor(enable); } - virtual bool SetLocalRenderer(VideoRenderer* renderer) { - return video_.SetLocalRenderer(renderer); - } - virtual const std::vector<AudioCodec>& audio_codecs() { return voice_.codecs(); } @@ -361,7 +351,6 @@ class NullVideoEngine { bool SetDefaultEncoderConfig(const VideoEncoderConfig& config) { return true; } - bool SetLocalRenderer(VideoRenderer* renderer) { return true; } const std::vector<VideoCodec>& codecs() { return codecs_; } const std::vector<RtpHeaderExtension>& rtp_header_extensions() { return rtp_header_extensions_; diff --git a/media/base/testutils.cc b/media/base/testutils.cc index 8b79df4..84fd05c 100644 --- a/media/base/testutils.cc +++ b/media/base/testutils.cc @@ -29,6 +29,7 @@ #include <math.h> +#include "talk/media/base/executablehelpers.h" #include "talk/media/base/rtpdump.h" #include "talk/media/base/videocapturer.h" #include "talk/media/base/videoframe.h" @@ -255,10 +256,15 @@ void VideoCapturerListener::OnFrameCaptured(VideoCapturer* capturer, // Returns the absolute path to a file in the testdata/ directory. std::string GetTestFilePath(const std::string& filename) { // Locate test data directory. +#ifdef ENABLE_WEBRTC + rtc::Pathname path = rtc::GetExecutablePath(); + EXPECT_FALSE(path.empty()); + path.AppendPathname("../../talk/"); +#else rtc::Pathname path = testing::GetTalkDirectory(); EXPECT_FALSE(path.empty()); // must be run from inside "talk" - path.AppendFolder("media"); - path.AppendFolder("testdata"); +#endif + path.AppendFolder("media/testdata/"); path.SetFilename(filename); return path.pathname(); } diff --git a/media/base/videoadapter_unittest.cc b/media/base/videoadapter_unittest.cc index af374e0..04bf3d1 100755 --- a/media/base/videoadapter_unittest.cc +++ b/media/base/videoadapter_unittest.cc @@ -69,17 +69,24 @@ class VideoAdapterTest : public testing::Test { listener_.get(), &VideoCapturerListener::OnFrameCaptured); } - void VerifyAdaptedResolution(int width, int height) { - EXPECT_TRUE(NULL != listener_->adapted_frame()); - EXPECT_EQ(static_cast<size_t>(width), - listener_->adapted_frame()->GetWidth()); - EXPECT_EQ(static_cast<size_t>(height), - listener_->adapted_frame()->GetHeight()); + virtual void TearDown() { + // Explicitly disconnect the VideoCapturer before to avoid data races + // (frames delivered to VideoCapturerListener while it's being destructed). + capturer_->SignalFrameCaptured.disconnect_all(); } protected: class VideoCapturerListener: public sigslot::has_slots<> { public: + struct Stats { + int captured_frames; + int dropped_frames; + bool last_adapt_was_no_op; + + int adapted_width; + int adapted_height; + }; + explicit VideoCapturerListener(VideoAdapter* adapter) : video_adapter_(adapter), adapted_frame_(NULL), @@ -95,6 +102,7 @@ class VideoAdapterTest : public testing::Test { EXPECT_TRUE(temp_i420.Init(captured_frame, captured_frame->width, abs(captured_frame->height))); VideoFrame* out_frame = NULL; + rtc::CritScope lock(&crit_); EXPECT_TRUE(video_adapter_->AdaptFrame(&temp_i420, &out_frame)); if (out_frame) { if (out_frame == &temp_i420) { @@ -112,12 +120,32 @@ class VideoAdapterTest : public testing::Test { ++captured_frames_; } - const VideoFrame* adapted_frame() const { return adapted_frame_; } - int captured_frames() const { return captured_frames_; } - int dropped_frames() const { return dropped_frames_; } - bool last_adapt_was_no_op() const { return last_adapt_was_no_op_; } + Stats GetStats() { + rtc::CritScope lock(&crit_); + Stats stats; + stats.captured_frames = captured_frames_; + stats.dropped_frames = dropped_frames_; + stats.last_adapt_was_no_op = last_adapt_was_no_op_; + if (adapted_frame_ != NULL) { + stats.adapted_width = static_cast<int>(adapted_frame_->GetWidth()); + stats.adapted_height = static_cast<int>(adapted_frame_->GetHeight()); + } else { + stats.adapted_width = stats.adapted_height = -1; + } + + return stats; + } + + VideoFrame* CopyAdaptedFrame() { + rtc::CritScope lock(&crit_); + if (adapted_frame_ == NULL) { + return NULL; + } + return adapted_frame_->Copy(); + } private: + rtc::CriticalSection crit_; VideoAdapter* video_adapter_; const VideoFrame* adapted_frame_; rtc::scoped_ptr<VideoFrame> copied_output_frame_; @@ -135,6 +163,13 @@ class VideoAdapterTest : public testing::Test { bool received_cpu_signal_; }; + void VerifyAdaptedResolution(const VideoCapturerListener::Stats& stats, + int width, + int height) { + EXPECT_EQ(width, stats.adapted_width); + EXPECT_EQ(height, stats.adapted_height); + } + rtc::scoped_ptr<FileVideoCapturer> capturer_; rtc::scoped_ptr<VideoAdapter> adapter_; rtc::scoped_ptr<VideoCapturerListener> listener_; @@ -157,12 +192,13 @@ TEST_F(VideoAdapterTest, AdaptInactive) { // Call Adapter with some frames. EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 10, kWaitTimeout); + listener_->GetStats().captured_frames >= 10, kWaitTimeout); // Verify no frame drop and no resolution change. - EXPECT_GE(listener_->captured_frames(), 10); - EXPECT_EQ(0, listener_->dropped_frames()); - VerifyAdaptedResolution(capture_format_.width, capture_format_.height); + VideoCapturerListener::Stats stats = listener_->GetStats(); + EXPECT_GE(stats.captured_frames, 10); + EXPECT_EQ(0, stats.dropped_frames); + VerifyAdaptedResolution(stats, capture_format_.width, capture_format_.height); } // Do not adapt the frame rate or the resolution. Expect no frame drop and no @@ -171,13 +207,14 @@ TEST_F(VideoAdapterTest, AdaptNothing) { adapter_->SetOutputFormat(capture_format_); EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 10, kWaitTimeout); + listener_->GetStats().captured_frames >= 10, kWaitTimeout); // Verify no frame drop and no resolution change. - EXPECT_GE(listener_->captured_frames(), 10); - EXPECT_EQ(0, listener_->dropped_frames()); - VerifyAdaptedResolution(capture_format_.width, capture_format_.height); - EXPECT_TRUE(listener_->last_adapt_was_no_op()); + VideoCapturerListener::Stats stats = listener_->GetStats(); + EXPECT_GE(stats.captured_frames, 10); + EXPECT_EQ(0, stats.dropped_frames); + VerifyAdaptedResolution(stats, capture_format_.width, capture_format_.height); + EXPECT_TRUE(stats.last_adapt_was_no_op); } TEST_F(VideoAdapterTest, AdaptZeroInterval) { @@ -187,12 +224,13 @@ TEST_F(VideoAdapterTest, AdaptZeroInterval) { adapter_->SetOutputFormat(format); EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 10, kWaitTimeout); + listener_->GetStats().captured_frames >= 10, kWaitTimeout); // Verify no crash and that frames aren't dropped. - EXPECT_GE(listener_->captured_frames(), 10); - EXPECT_EQ(0, listener_->dropped_frames()); - VerifyAdaptedResolution(capture_format_.width, capture_format_.height); + VideoCapturerListener::Stats stats = listener_->GetStats(); + EXPECT_GE(stats.captured_frames, 10); + EXPECT_EQ(0, stats.dropped_frames); + VerifyAdaptedResolution(stats, capture_format_.width, capture_format_.height); } // Adapt the frame rate to be half of the capture rate at the beginning. Expect @@ -203,12 +241,13 @@ TEST_F(VideoAdapterTest, AdaptFramerate) { adapter_->SetOutputFormat(request_format); EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 10, kWaitTimeout); + listener_->GetStats().captured_frames >= 10, kWaitTimeout); // Verify frame drop and no resolution change. - EXPECT_GE(listener_->captured_frames(), 10); - EXPECT_EQ(listener_->captured_frames() / 2, listener_->dropped_frames()); - VerifyAdaptedResolution(capture_format_.width, capture_format_.height); + VideoCapturerListener::Stats stats = listener_->GetStats(); + EXPECT_GE(stats.captured_frames, 10); + EXPECT_EQ(stats.captured_frames / 2, stats.dropped_frames); + VerifyAdaptedResolution(stats, capture_format_.width, capture_format_.height); } // Adapt the frame rate to be half of the capture rate at the beginning. Expect @@ -219,13 +258,14 @@ TEST_F(VideoAdapterTest, AdaptFramerateVariable) { adapter_->SetOutputFormat(request_format); EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 30, kWaitTimeout); + listener_->GetStats().captured_frames >= 30, kWaitTimeout); // Verify frame drop and no resolution change. - EXPECT_GE(listener_->captured_frames(), 30); + VideoCapturerListener::Stats stats = listener_->GetStats(); + EXPECT_GE(stats.captured_frames, 30); // Verify 2 / 3 kept (20) and 1 / 3 dropped (10). - EXPECT_EQ(listener_->captured_frames() * 1 / 3, listener_->dropped_frames()); - VerifyAdaptedResolution(capture_format_.width, capture_format_.height); + EXPECT_EQ(stats.captured_frames * 1 / 3, stats.dropped_frames); + VerifyAdaptedResolution(stats, capture_format_.width, capture_format_.height); } // Adapt the frame rate to be half of the capture rate after capturing no less @@ -236,20 +276,20 @@ TEST_F(VideoAdapterTest, AdaptFramerateOntheFly) { adapter_->SetOutputFormat(request_format); EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 10, kWaitTimeout); + listener_->GetStats().captured_frames >= 10, kWaitTimeout); // Verify no frame drop before adaptation. - EXPECT_EQ(0, listener_->dropped_frames()); + EXPECT_EQ(0, listener_->GetStats().dropped_frames); // Adapat the frame rate. request_format.interval *= 2; adapter_->SetOutputFormat(request_format); EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 20, kWaitTimeout); + listener_->GetStats().captured_frames >= 20, kWaitTimeout); // Verify frame drop after adaptation. - EXPECT_GT(listener_->dropped_frames(), 0); + EXPECT_GT(listener_->GetStats().dropped_frames, 0); } // Adapt the frame resolution to be a quarter of the capture resolution at the @@ -261,11 +301,12 @@ TEST_F(VideoAdapterTest, AdaptResolution) { adapter_->SetOutputFormat(request_format); EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 10, kWaitTimeout); + listener_->GetStats().captured_frames >= 10, kWaitTimeout); // Verify no frame drop and resolution change. - EXPECT_EQ(0, listener_->dropped_frames()); - VerifyAdaptedResolution(request_format.width, request_format.height); + VideoCapturerListener::Stats stats = listener_->GetStats(); + EXPECT_EQ(0, stats.dropped_frames); + VerifyAdaptedResolution(stats, request_format.width, request_format.height); } // Adapt the frame resolution to half width. Expect resolution change. @@ -276,10 +317,10 @@ TEST_F(VideoAdapterTest, AdaptResolutionNarrow) { adapter_->SetOutputFormat(request_format); EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 10, kWaitTimeout); + listener_->GetStats().captured_frames >= 10, kWaitTimeout); // Verify resolution change. - VerifyAdaptedResolution(213, 160); + VerifyAdaptedResolution(listener_->GetStats(), 213, 160); } // Adapt the frame resolution to half height. Expect resolution change. @@ -290,10 +331,10 @@ TEST_F(VideoAdapterTest, AdaptResolutionWide) { adapter_->SetOutputFormat(request_format); EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 10, kWaitTimeout); + listener_->GetStats().captured_frames >= 10, kWaitTimeout); // Verify resolution change. - VerifyAdaptedResolution(213, 160); + VerifyAdaptedResolution(listener_->GetStats(), 213, 160); } // Adapt the frame resolution to be a quarter of the capture resolution after @@ -304,21 +345,25 @@ TEST_F(VideoAdapterTest, AdaptResolutionOnTheFly) { adapter_->SetOutputFormat(request_format); EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 10, kWaitTimeout); + listener_->GetStats().captured_frames >= 10, kWaitTimeout); // Verify no resolution change before adaptation. - VerifyAdaptedResolution(request_format.width, request_format.height); + VerifyAdaptedResolution( + listener_->GetStats(), request_format.width, request_format.height); // Adapt the frame resolution. request_format.width /= 2; request_format.height /= 2; adapter_->SetOutputFormat(request_format); - EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 20, kWaitTimeout); - + int captured_frames = listener_->GetStats().captured_frames; + EXPECT_TRUE_WAIT( + !capturer_->IsRunning() || + listener_->GetStats().captured_frames >= captured_frames + 10, + kWaitTimeout); // Verify resolution change after adaptation. - VerifyAdaptedResolution(request_format.width, request_format.height); + VerifyAdaptedResolution( + listener_->GetStats(), request_format.width, request_format.height); } // Black the output frame. @@ -326,42 +371,57 @@ TEST_F(VideoAdapterTest, BlackOutput) { adapter_->SetOutputFormat(capture_format_); EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 10, kWaitTimeout); + listener_->GetStats().captured_frames >= 10, kWaitTimeout); // Verify that the output frame is not black. - EXPECT_NE(16, *listener_->adapted_frame()->GetYPlane()); - EXPECT_NE(128, *listener_->adapted_frame()->GetUPlane()); - EXPECT_NE(128, *listener_->adapted_frame()->GetVPlane()); + rtc::scoped_ptr<VideoFrame> adapted_frame(listener_->CopyAdaptedFrame()); + EXPECT_NE(16, *adapted_frame->GetYPlane()); + EXPECT_NE(128, *adapted_frame->GetUPlane()); + EXPECT_NE(128, *adapted_frame->GetVPlane()); adapter_->SetBlackOutput(true); - EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 20, kWaitTimeout); + int captured_frames = listener_->GetStats().captured_frames; + EXPECT_TRUE_WAIT( + !capturer_->IsRunning() || + listener_->GetStats().captured_frames >= captured_frames + 10, + kWaitTimeout); // Verify that the output frame is black. - EXPECT_EQ(16, *listener_->adapted_frame()->GetYPlane()); - EXPECT_EQ(128, *listener_->adapted_frame()->GetUPlane()); - EXPECT_EQ(128, *listener_->adapted_frame()->GetVPlane()); + adapted_frame.reset(listener_->CopyAdaptedFrame()); + EXPECT_EQ(16, *adapted_frame->GetYPlane()); + EXPECT_EQ(128, *adapted_frame->GetUPlane()); + EXPECT_EQ(128, *adapted_frame->GetVPlane()); // Verify that the elapsed time and timestamp of the black frame increase. - int64 elapsed_time = listener_->adapted_frame()->GetElapsedTime(); - int64 timestamp = listener_->adapted_frame()->GetTimeStamp(); - EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 22, kWaitTimeout); - EXPECT_GT(listener_->adapted_frame()->GetElapsedTime(), elapsed_time); - EXPECT_GT(listener_->adapted_frame()->GetTimeStamp(), timestamp); + int64 elapsed_time = adapted_frame->GetElapsedTime(); + int64 timestamp = adapted_frame->GetTimeStamp(); + captured_frames = listener_->GetStats().captured_frames; + EXPECT_TRUE_WAIT( + !capturer_->IsRunning() || + listener_->GetStats().captured_frames >= captured_frames + 10, + kWaitTimeout); + + adapted_frame.reset(listener_->CopyAdaptedFrame()); + EXPECT_GT(adapted_frame->GetElapsedTime(), elapsed_time); + EXPECT_GT(adapted_frame->GetTimeStamp(), timestamp); // Change the output size VideoFormat request_format = capture_format_; request_format.width /= 2; request_format.height /= 2; adapter_->SetOutputFormat(request_format); + captured_frames = listener_->GetStats().captured_frames; + EXPECT_TRUE_WAIT( + !capturer_->IsRunning() || + listener_->GetStats().captured_frames >= captured_frames + 10, + kWaitTimeout); - EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 40, kWaitTimeout); // Verify resolution change after adaptation. - VerifyAdaptedResolution(request_format.width, request_format.height); + VerifyAdaptedResolution( + listener_->GetStats(), request_format.width, request_format.height); // Verify that the output frame is black. - EXPECT_EQ(16, *listener_->adapted_frame()->GetYPlane()); - EXPECT_EQ(128, *listener_->adapted_frame()->GetUPlane()); - EXPECT_EQ(128, *listener_->adapted_frame()->GetVPlane()); + adapted_frame.reset(listener_->CopyAdaptedFrame()); + EXPECT_EQ(16, *adapted_frame->GetYPlane()); + EXPECT_EQ(128, *adapted_frame->GetUPlane()); + EXPECT_EQ(128, *adapted_frame->GetVPlane()); } // Drop all frames. @@ -370,11 +430,12 @@ TEST_F(VideoAdapterTest, DropAllFrames) { adapter_->SetOutputFormat(format); EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); EXPECT_TRUE_WAIT(!capturer_->IsRunning() || - listener_->captured_frames() >= 10, kWaitTimeout); + listener_->GetStats().captured_frames >= 10, kWaitTimeout); // Verify all frames are dropped. - EXPECT_GE(listener_->captured_frames(), 10); - EXPECT_EQ(listener_->captured_frames(), listener_->dropped_frames()); + VideoCapturerListener::Stats stats = listener_->GetStats(); + EXPECT_GE(stats.captured_frames, 10); + EXPECT_EQ(stats.captured_frames, stats.dropped_frames); } TEST(CoordinatedVideoAdapterTest, TestCoordinatedWithoutCpuAdaptation) { diff --git a/media/base/videoengine_unittest.h b/media/base/videoengine_unittest.h index 0f03c7b..8eab347 100644 --- a/media/base/videoengine_unittest.h +++ b/media/base/videoengine_unittest.h @@ -1593,6 +1593,25 @@ class VideoMediaChannelTest : public testing::Test, frame_count += 2; EXPECT_EQ_WAIT(frame_count, renderer_.num_rendered_frames(), kTimeout); } + // Tests that adapted frames won't be upscaled to a higher resolution. + void SendsLowerResolutionOnSmallerFrames() { + cricket::VideoCodec codec = DefaultCodec(); + codec.width = 320; + codec.height = 240; + EXPECT_TRUE(SetOneCodec(codec)); + EXPECT_TRUE(SetSend(true)); + EXPECT_TRUE(channel_->SetRender(true)); + EXPECT_TRUE(channel_->SetRenderer(kDefaultReceiveSsrc, &renderer_)); + EXPECT_EQ(0, renderer_.num_rendered_frames()); + EXPECT_TRUE(SendFrame()); + EXPECT_FRAME_WAIT(1, codec.width, codec.height, kTimeout); + + // Check that we send smaller frames at the new resolution. + EXPECT_TRUE(rtc::Thread::Current()->ProcessMessages(33)); + EXPECT_TRUE(video_capturer_->CaptureCustomFrame( + codec.width / 2, codec.height / 2, cricket::FOURCC_I420)); + EXPECT_FRAME_WAIT(2, codec.width / 2, codec.height / 2, kTimeout); + } // Tests that we can set the send stream format properly. void SetSendStreamFormat() { cricket::VideoCodec codec(DefaultCodec()); diff --git a/media/base/videoframe.cc b/media/base/videoframe.cc index 1c5cfd8..018d065 100644 --- a/media/base/videoframe.cc +++ b/media/base/videoframe.cc @@ -235,7 +235,7 @@ bool VideoFrame::SetToBlack() { } static const size_t kMaxSampleSize = 1000000000u; -// Returns whether a sample is valid +// Returns whether a sample is valid. bool VideoFrame::Validate(uint32 fourcc, int w, int h, const uint8 *sample, size_t sample_size) { if (h < 0) { @@ -311,6 +311,11 @@ bool VideoFrame::Validate(uint32 fourcc, int w, int h, << " " << sample_size; return false; } + // TODO(fbarchard): Make function to dump information about frames. + uint8 four_samples[4] = { 0, 0, 0, 0 }; + for (size_t i = 0; i < ARRAY_SIZE(four_samples) && i < sample_size; ++i) { + four_samples[i] = sample[i]; + } if (sample_size < expected_size) { LOG(LS_ERROR) << "Size field is too small." << " format: " << GetFourccName(format) @@ -318,10 +323,10 @@ bool VideoFrame::Validate(uint32 fourcc, int w, int h, << " size: " << w << "x" << h << " " << sample_size << " expected: " << expected_size - << " sample[0..3]: " << static_cast<int>(sample[0]) - << ", " << static_cast<int>(sample[1]) - << ", " << static_cast<int>(sample[2]) - << ", " << static_cast<int>(sample[3]); + << " sample[0..3]: " << static_cast<int>(four_samples[0]) + << ", " << static_cast<int>(four_samples[1]) + << ", " << static_cast<int>(four_samples[2]) + << ", " << static_cast<int>(four_samples[3]); return false; } if (sample_size > kMaxSampleSize) { @@ -331,13 +336,14 @@ bool VideoFrame::Validate(uint32 fourcc, int w, int h, << " size: " << w << "x" << h << " " << sample_size << " expected: " << 2 * expected_size - << " sample[0..3]: " << static_cast<int>(sample[0]) - << ", " << static_cast<int>(sample[1]) - << ", " << static_cast<int>(sample[2]) - << ", " << static_cast<int>(sample[3]); + << " sample[0..3]: " << static_cast<int>(four_samples[0]) + << ", " << static_cast<int>(four_samples[1]) + << ", " << static_cast<int>(four_samples[2]) + << ", " << static_cast<int>(four_samples[3]); return false; } // Show large size warning once every 100 frames. + // TODO(fbarchard): Make frame counter atomic for thread safety. static int large_warn100 = 0; size_t large_expected_size = expected_size * 2; if (expected_bpp >= 8 && @@ -350,27 +356,14 @@ bool VideoFrame::Validate(uint32 fourcc, int w, int h, << " size: " << w << "x" << h << " bytes: " << sample_size << " expected: " << large_expected_size - << " sample[0..3]: " << static_cast<int>(sample[0]) - << ", " << static_cast<int>(sample[1]) - << ", " << static_cast<int>(sample[2]) - << ", " << static_cast<int>(sample[3]); - } - // Scan pages to ensure they are there and don't contain a single value and - // to generate an error. - if (!memcmp(sample + sample_size - 8, sample + sample_size - 4, 4) && - !memcmp(sample, sample + 4, sample_size - 4)) { - LOG(LS_WARNING) << "Duplicate value for all pixels." - << " format: " << GetFourccName(format) - << " bpp: " << expected_bpp - << " size: " << w << "x" << h - << " bytes: " << sample_size - << " expected: " << expected_size - << " sample[0..3]: " << static_cast<int>(sample[0]) - << ", " << static_cast<int>(sample[1]) - << ", " << static_cast<int>(sample[2]) - << ", " << static_cast<int>(sample[3]); + << " sample[0..3]: " << static_cast<int>(four_samples[0]) + << ", " << static_cast<int>(four_samples[1]) + << ", " << static_cast<int>(four_samples[2]) + << ", " << static_cast<int>(four_samples[3]); } + // TODO(fbarchard): Add duplicate pixel check. + // TODO(fbarchard): Use frame counter atomic for thread safety. static bool valid_once = true; if (valid_once) { valid_once = false; @@ -380,10 +373,10 @@ bool VideoFrame::Validate(uint32 fourcc, int w, int h, << " size: " << w << "x" << h << " bytes: " << sample_size << " expected: " << expected_size - << " sample[0..3]: " << static_cast<int>(sample[0]) - << ", " << static_cast<int>(sample[1]) - << ", " << static_cast<int>(sample[2]) - << ", " << static_cast<int>(sample[3]); + << " sample[0..3]: " << static_cast<int>(four_samples[0]) + << ", " << static_cast<int>(four_samples[1]) + << ", " << static_cast<int>(four_samples[2]) + << ", " << static_cast<int>(four_samples[3]); } return true; } diff --git a/media/base/videoframe_unittest.h b/media/base/videoframe_unittest.h index c4a7a8c..483fc34 100644 --- a/media/base/videoframe_unittest.h +++ b/media/base/videoframe_unittest.h @@ -135,6 +135,9 @@ class VideoFrameTest : public testing::Test { rtc::scoped_ptr<rtc::FileStream> fs( rtc::Filesystem::OpenFile(path, "rb")); if (!fs.get()) { + LOG(LS_ERROR) << "Could not open test file path: " << path.pathname() + << " from current dir " + << rtc::Filesystem::GetCurrentDirectory().pathname(); return NULL; } @@ -143,6 +146,7 @@ class VideoFrameTest : public testing::Test { new rtc::MemoryStream()); rtc::StreamResult res = Flow(fs.get(), buf, sizeof(buf), ms.get()); if (res != rtc::SR_SUCCESS) { + LOG(LS_ERROR) << "Could not load test file path: " << path.pathname(); return NULL; } @@ -419,17 +423,22 @@ class VideoFrameTest : public testing::Test { const uint8* u, uint32 upitch, const uint8* v, uint32 vpitch, int max_error) { - return IsSize(frame, width, height) && + return IsSize(frame, + static_cast<uint32>(width), + static_cast<uint32>(height)) && frame.GetPixelWidth() == pixel_width && frame.GetPixelHeight() == pixel_height && frame.GetElapsedTime() == elapsed_time && frame.GetTimeStamp() == time_stamp && IsPlaneEqual("y", frame.GetYPlane(), frame.GetYPitch(), y, ypitch, - width, height, max_error) && + static_cast<uint32>(width), + static_cast<uint32>(height), max_error) && IsPlaneEqual("u", frame.GetUPlane(), frame.GetUPitch(), u, upitch, - (width + 1) / 2, (height + 1) / 2, max_error) && + static_cast<uint32>((width + 1) / 2), + static_cast<uint32>((height + 1) / 2), max_error) && IsPlaneEqual("v", frame.GetVPlane(), frame.GetVPitch(), v, vpitch, - (width + 1) / 2, (height + 1) / 2, max_error); + static_cast<uint32>((width + 1) / 2), + static_cast<uint32>((height + 1) / 2), max_error); } static bool IsEqual(const cricket::VideoFrame& frame1, @@ -719,7 +728,7 @@ class VideoFrameTest : public testing::Test { T frame1, frame2; size_t out_size = kWidth * kHeight * 2; rtc::scoped_ptr<uint8[]> outbuf(new uint8[out_size + kAlignment]); - uint8 *out = ALIGNP(outbuf.get(), kAlignment); + uint8* out = ALIGNP(outbuf.get(), kAlignment); T frame; ASSERT_TRUE(LoadFrameNoRepeat(&frame1)); EXPECT_EQ(out_size, frame1.ConvertToRgbBuffer(cricket::FOURCC_RGBP, @@ -735,7 +744,7 @@ class VideoFrameTest : public testing::Test { T frame1, frame2; size_t out_size = kWidth * kHeight * 2; rtc::scoped_ptr<uint8[]> outbuf(new uint8[out_size + kAlignment]); - uint8 *out = ALIGNP(outbuf.get(), kAlignment); + uint8* out = ALIGNP(outbuf.get(), kAlignment); T frame; ASSERT_TRUE(LoadFrameNoRepeat(&frame1)); EXPECT_EQ(out_size, frame1.ConvertToRgbBuffer(cricket::FOURCC_RGBO, @@ -751,7 +760,7 @@ class VideoFrameTest : public testing::Test { T frame1, frame2; size_t out_size = kWidth * kHeight * 2; rtc::scoped_ptr<uint8[]> outbuf(new uint8[out_size + kAlignment]); - uint8 *out = ALIGNP(outbuf.get(), kAlignment); + uint8* out = ALIGNP(outbuf.get(), kAlignment); T frame; ASSERT_TRUE(LoadFrameNoRepeat(&frame1)); EXPECT_EQ(out_size, frame1.ConvertToRgbBuffer(cricket::FOURCC_R444, @@ -771,12 +780,12 @@ class VideoFrameTest : public testing::Test { size_t bayer_size = kWidth * kHeight; \ rtc::scoped_ptr<uint8[]> bayerbuf(new uint8[ \ bayer_size + kAlignment]); \ - uint8 *bayer = ALIGNP(bayerbuf.get(), kAlignment); \ + uint8* bayer = ALIGNP(bayerbuf.get(), kAlignment); \ T frame1, frame2; \ rtc::scoped_ptr<rtc::MemoryStream> ms( \ CreateRgbSample(cricket::FOURCC_ARGB, kWidth, kHeight)); \ ASSERT_TRUE(ms.get() != NULL); \ - libyuv::ARGBToBayer##BAYER(reinterpret_cast<uint8 *>(ms->GetBuffer()), \ + libyuv::ARGBToBayer##BAYER(reinterpret_cast<uint8* >(ms->GetBuffer()), \ kWidth * 4, \ bayer, kWidth, \ kWidth, kHeight); \ @@ -812,8 +821,8 @@ void Construct##FOURCC##Mirror() { \ reinterpret_cast<uint8*>(ms->GetBuffer()), \ data_size, \ 1, 1, 0, 0, 0)); \ - int width_rotate = frame1.GetWidth(); \ - int height_rotate = frame1.GetHeight(); \ + int width_rotate = static_cast<int>(frame1.GetWidth()); \ + int height_rotate = static_cast<int>(frame1.GetHeight()); \ EXPECT_TRUE(frame3.InitToBlack(width_rotate, height_rotate, 1, 1, 0, 0)); \ libyuv::I420Mirror(frame2.GetYPlane(), frame2.GetYPitch(), \ frame2.GetUPlane(), frame2.GetUPitch(), \ @@ -845,8 +854,8 @@ void Construct##FOURCC##Rotate##ROTATE() { \ reinterpret_cast<uint8*>(ms->GetBuffer()), \ data_size, \ 1, 1, 0, 0, 0)); \ - int width_rotate = frame1.GetWidth(); \ - int height_rotate = frame1.GetHeight(); \ + int width_rotate = static_cast<int>(frame1.GetWidth()); \ + int height_rotate = static_cast<int>(frame1.GetHeight()); \ EXPECT_TRUE(frame3.InitToBlack(width_rotate, height_rotate, 1, 1, 0, 0)); \ libyuv::I420Rotate(frame2.GetYPlane(), frame2.GetYPitch(), \ frame2.GetUPlane(), frame2.GetUPitch(), \ @@ -995,7 +1004,7 @@ void Construct##FOURCC##Rotate##ROTATE() { \ // Convert back to ARGB. size_t out_size = 4; rtc::scoped_ptr<uint8[]> outbuf(new uint8[out_size + kAlignment]); - uint8 *out = ALIGNP(outbuf.get(), kAlignment); + uint8* out = ALIGNP(outbuf.get(), kAlignment); EXPECT_EQ(out_size, frame.ConvertToRgbBuffer(cricket::FOURCC_ARGB, out, @@ -1032,7 +1041,7 @@ void Construct##FOURCC##Rotate##ROTATE() { \ // Convert back to ARGB size_t out_size = 10 * 4; rtc::scoped_ptr<uint8[]> outbuf(new uint8[out_size + kAlignment]); - uint8 *out = ALIGNP(outbuf.get(), kAlignment); + uint8* out = ALIGNP(outbuf.get(), kAlignment); EXPECT_EQ(out_size, frame.ConvertToRgbBuffer(cricket::FOURCC_ARGB, out, @@ -1431,8 +1440,8 @@ void Construct##FOURCC##Rotate##ROTATE() { \ size_t out_size = astride * kHeight; rtc::scoped_ptr<uint8[]> outbuf(new uint8[out_size + kAlignment + 1]); memset(outbuf.get(), 0, out_size + kAlignment + 1); - uint8 *outtop = ALIGNP(outbuf.get(), kAlignment); - uint8 *out = outtop; + uint8* outtop = ALIGNP(outbuf.get(), kAlignment); + uint8* out = outtop; int stride = astride; if (invert) { out += (kHeight - 1) * stride; // Point to last row. @@ -1869,7 +1878,7 @@ void Construct##FOURCC##Rotate##ROTATE() { \ size_t bayer_size = kWidth * kHeight; \ rtc::scoped_ptr<uint8[]> bayerbuf(new uint8[ \ bayer_size + kAlignment]); \ - uint8 *bayer = ALIGNP(bayerbuf.get(), kAlignment); \ + uint8* bayer = ALIGNP(bayerbuf.get(), kAlignment); \ T frame; \ rtc::scoped_ptr<rtc::MemoryStream> ms( \ CreateRgbSample(cricket::FOURCC_ARGB, kWidth, kHeight)); \ @@ -1898,7 +1907,7 @@ void Construct##FOURCC##Rotate##ROTATE() { \ size_t bayer_size = kWidth * kHeight; \ rtc::scoped_ptr<uint8[]> bayerbuf(new uint8[ \ bayer_size + 1 + kAlignment]); \ - uint8 *bayer = ALIGNP(bayerbuf.get(), kAlignment) + 1; \ + uint8* bayer = ALIGNP(bayerbuf.get(), kAlignment) + 1; \ T frame; \ rtc::scoped_ptr<rtc::MemoryStream> ms( \ CreateRgbSample(cricket::FOURCC_ARGB, kWidth, kHeight)); \ @@ -1935,7 +1944,7 @@ void Construct##FOURCC##Rotate##ROTATE() { \ size_t bayer_size = kWidth * kHeight; \ rtc::scoped_ptr<uint8[]> bayerbuf(new uint8[ \ bayer_size + kAlignment]); \ - uint8 *bayer1 = ALIGNP(bayerbuf.get(), kAlignment); \ + uint8* bayer1 = ALIGNP(bayerbuf.get(), kAlignment); \ for (int i = 0; i < kWidth * kHeight; ++i) { \ bayer1[i] = static_cast<uint8>(i * 33u + 183u); \ } \ @@ -1951,7 +1960,7 @@ void Construct##FOURCC##Rotate##ROTATE() { \ } \ rtc::scoped_ptr<uint8[]> bayer2buf(new uint8[ \ bayer_size + kAlignment]); \ - uint8 *bayer2 = ALIGNP(bayer2buf.get(), kAlignment); \ + uint8* bayer2 = ALIGNP(bayer2buf.get(), kAlignment); \ libyuv::ARGBToBayer##BAYER(reinterpret_cast<uint8*>(ms->GetBuffer()), \ kWidth * 4, \ bayer2, kWidth, \ diff --git a/media/devices/linuxdevicemanager.cc b/media/devices/linuxdevicemanager.cc index a79e226..d122169 100644 --- a/media/devices/linuxdevicemanager.cc +++ b/media/devices/linuxdevicemanager.cc @@ -31,10 +31,10 @@ #include "talk/media/base/mediacommon.h" #include "talk/media/devices/libudevsymboltable.h" #include "talk/media/devices/v4llookup.h" -#include "talk/sound/platformsoundsystem.h" -#include "talk/sound/platformsoundsystemfactory.h" -#include "talk/sound/sounddevicelocator.h" -#include "talk/sound/soundsysteminterface.h" +#include "webrtc/sound/platformsoundsystem.h" +#include "webrtc/sound/platformsoundsystemfactory.h" +#include "webrtc/sound/sounddevicelocator.h" +#include "webrtc/sound/soundsysteminterface.h" #include "webrtc/base/fileutils.h" #include "webrtc/base/linux.h" #include "webrtc/base/logging.h" @@ -89,7 +89,7 @@ static const char* kFilteredVideoDevicesName[] = { }; LinuxDeviceManager::LinuxDeviceManager() - : sound_system_(new PlatformSoundSystemFactory()) { + : sound_system_(new rtc::PlatformSoundSystemFactory()) { set_watcher(new LinuxDeviceWatcher(this)); } @@ -102,7 +102,7 @@ bool LinuxDeviceManager::GetAudioDevices(bool input, if (!sound_system_.get()) { return false; } - SoundSystemInterface::SoundDeviceLocatorList list; + rtc::SoundSystemInterface::SoundDeviceLocatorList list; bool success; if (input) { success = sound_system_->EnumerateCaptureDevices(&list); @@ -118,12 +118,12 @@ bool LinuxDeviceManager::GetAudioDevices(bool input, // device at index 0, but Enumerate(Capture|Playback)Devices does not include // a locator for the default device. int index = 1; - for (SoundSystemInterface::SoundDeviceLocatorList::iterator i = list.begin(); + for (rtc::SoundSystemInterface::SoundDeviceLocatorList::iterator i = list.begin(); i != list.end(); ++i, ++index) { devs->push_back(Device((*i)->name(), index)); } - SoundSystemInterface::ClearSoundDeviceLocatorList(&list); + rtc::SoundSystemInterface::ClearSoundDeviceLocatorList(&list); sound_system_.release(); return FilterDevices(devs, kFilteredAudioDevicesName); } diff --git a/media/devices/linuxdevicemanager.h b/media/devices/linuxdevicemanager.h index 88aee4e..1eb648f 100644 --- a/media/devices/linuxdevicemanager.h +++ b/media/devices/linuxdevicemanager.h @@ -32,7 +32,7 @@ #include <vector> #include "talk/media/devices/devicemanager.h" -#include "talk/sound/soundsystemfactory.h" +#include "webrtc/sound/soundsystemfactory.h" #include "webrtc/base/sigslot.h" #include "webrtc/base/stringencode.h" @@ -47,7 +47,7 @@ class LinuxDeviceManager : public DeviceManager { private: virtual bool GetAudioDevices(bool input, std::vector<Device>* devs); - SoundSystemHandle sound_system_; + rtc::SoundSystemHandle sound_system_; }; } // namespace cricket diff --git a/media/devices/macdevicemanager.cc b/media/devices/macdevicemanager.cc index 568ee53..8f777b4 100644 --- a/media/devices/macdevicemanager.cc +++ b/media/devices/macdevicemanager.cc @@ -71,7 +71,7 @@ static const UInt32 kAudioDeviceNameLength = 64; extern DeviceWatcherImpl* CreateDeviceWatcherCallback( DeviceManagerInterface* dm); extern void ReleaseDeviceWatcherCallback(DeviceWatcherImpl* impl); -extern bool GetQTKitVideoDevices(std::vector<Device>* out); +extern bool GetAVFoundationVideoDevices(std::vector<Device>* out); static bool GetAudioDeviceIDs(bool inputs, std::vector<AudioDeviceID>* out); static bool GetAudioDeviceName(AudioDeviceID id, bool input, std::string* out); @@ -84,7 +84,7 @@ MacDeviceManager::~MacDeviceManager() { bool MacDeviceManager::GetVideoCaptureDevices(std::vector<Device>* devices) { devices->clear(); - if (!GetQTKitVideoDevices(devices)) { + if (!GetAVFoundationVideoDevices(devices)) { return false; } return FilterDevices(devices, kFilteredVideoDevicesName); diff --git a/media/devices/macdevicemanagermm.mm b/media/devices/macdevicemanagermm.mm index 3091ec4..cfcf5a4 100644 --- a/media/devices/macdevicemanagermm.mm +++ b/media/devices/macdevicemanagermm.mm @@ -33,6 +33,11 @@ #include "talk/media/devices/devicemanager.h" #import <assert.h> +#ifdef __MAC_OS_X_VERSION_MAX_ALLOWED +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 + #import <AVFoundation/AVFoundation.h> +#endif +#endif #import <QTKit/QTKit.h> #include "webrtc/base/logging.h" @@ -136,4 +141,52 @@ bool GetQTKitVideoDevices(std::vector<Device>* devices) { return true; } +bool GetAVFoundationVideoDevices(std::vector<Device>* devices) { +#ifdef __MAC_OS_X_VERSION_MAX_ALLOWED +#if __MAC_OS_X_VERSION_MAX_ALLOWED >=1070 + if (![AVCaptureDevice class]) { + // Fallback to using QTKit if AVFoundation is not available + return GetQTKitVideoDevices(devices); + } +#if !__has_feature(objc_arc) + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; +#else + @autoreleasepool +#endif + { + NSArray* capture_devices = [AVCaptureDevice devices]; + LOG(LS_INFO) << [capture_devices count] << " capture device(s) found:"; + for (AVCaptureDevice* capture_device in capture_devices) { + if ([capture_device hasMediaType:AVMediaTypeVideo] || + [capture_device hasMediaType:AVMediaTypeMuxed]) { + static NSString* const kFormat = @"localizedName: \"%@\", " + @"modelID: \"%@\", uniqueID \"%@\", isConnected: %d, " + @"isInUseByAnotherApplication: %d"; + NSString* info = [NSString + stringWithFormat:kFormat, + [capture_device localizedName], + [capture_device modelID], + [capture_device uniqueID], + [capture_device isConnected], + [capture_device isInUseByAnotherApplication]]; + LOG(LS_INFO) << [info UTF8String]; + + std::string name([[capture_device localizedName] UTF8String]); + devices->push_back( + Device(name, [[capture_device uniqueID] UTF8String])); + } + } + } +#if !__has_feature(objc_arc) + [pool drain]; +#endif + return true; +#else // __MAC_OS_X_VERSION_MAX_ALLOWED >=1070 + return GetQTKitVideoDevices(devices); +#endif // __MAC_OS_X_VERSION_MAX_ALLOWED >=1070 +#else // __MAC_OS_X_VERSION_MAX_ALLOWED + return GetQTKitVideoDevices(devices); +#endif // __MAC_OS_X_VERSION_MAX_ALLOWED +} + } // namespace cricket diff --git a/media/other/linphonemediaengine.h b/media/other/linphonemediaengine.h index e4ba345..b4950de 100644 --- a/media/other/linphonemediaengine.h +++ b/media/other/linphonemediaengine.h @@ -70,7 +70,6 @@ class LinphoneMediaEngine : public MediaEngineInterface { virtual VideoMediaChannel* CreateVideoChannel(VoiceMediaChannel* voice_ch); virtual SoundclipMedia* CreateSoundclip() { return NULL; } virtual bool SetAudioOptions(int options) { return true; } - virtual bool SetVideoOptions(int options) { return true; } virtual bool SetDefaultVideoEncoderConfig(const VideoEncoderConfig& config) { return true; } @@ -81,7 +80,6 @@ class LinphoneMediaEngine : public MediaEngineInterface { virtual bool SetOutputVolume(int level) { return true; } virtual int GetInputLevel() { return 0; } virtual bool SetLocalMonitor(bool enable) { return true; } - virtual bool SetLocalRenderer(VideoRenderer* renderer) { return true; } // TODO: control channel send? virtual bool SetVideoCapture(bool capture) { return true; } virtual const std::vector<AudioCodec>& audio_codecs() { diff --git a/media/sctp/sctpdataengine_unittest.cc b/media/sctp/sctpdataengine_unittest.cc index 0fb9b9d..fb00650 100644 --- a/media/sctp/sctpdataengine_unittest.cc +++ b/media/sctp/sctpdataengine_unittest.cc @@ -45,6 +45,11 @@ #include "webrtc/base/ssladapter.h" #include "webrtc/base/thread.h" +#ifdef HAVE_NSS_SSL_H +// TODO(thorcarpenter): Remove after webrtc switches over to BoringSSL. +#include "webrtc/base/nssstreamadapter.h" +#endif // HAVE_NSS_SSL_H + enum { MSG_PACKET = 1, }; @@ -69,8 +74,7 @@ class SctpFakeNetworkInterface : public cricket::MediaChannel::NetworkInterface, // TODO(ldixon): Can/should we use Buffer.TransferTo here? // Note: this assignment does a deep copy of data from packet. - rtc::Buffer* buffer = new rtc::Buffer(packet->data(), - packet->length()); + rtc::Buffer* buffer = new rtc::Buffer(packet->data(), packet->length()); thread_->Post(this, MSG_PACKET, rtc::WrapMessageData(buffer)); LOG(LS_VERBOSE) << "SctpFakeNetworkInterface::SendPacket, Posted message."; return true; @@ -219,6 +223,12 @@ class SctpDataMediaChannelTest : public testing::Test, // usrsctp uses the NSS random number generator on non-Android platforms, // so we need to initialize SSL. static void SetUpTestCase() { +#ifdef HAVE_NSS_SSL_H + // TODO(thorcarpenter): Remove after webrtc switches over to BoringSSL. + if (!rtc::NSSContext::InitializeSSL(NULL)) { + LOG(LS_WARNING) << "Unabled to initialize NSS."; + } +#endif // HAVE_NSS_SSL_H rtc::InitializeSSL(); } @@ -266,6 +276,10 @@ class SctpDataMediaChannelTest : public testing::Test, virtual void TearDown() { channel1()->SetSend(false); channel2()->SetSend(false); + + // Process messages until idle to prevent a sent packet from being dropped + // and causing memory leaks (not being deleted by the receiver). + ProcessMessagesUntilIdle(); } void AddStream(int ssrc) { diff --git a/media/testdata/faces.1280x720_P420.yuv b/media/testdata/faces.1280x720_P420.yuv new file mode 100644 index 0000000..f4844ee --- /dev/null +++ b/media/testdata/faces.1280x720_P420.yuv @@ -0,0 +1 @@ +IIIIIIIIIHHHHIIIIJJJJJIIIIIIJJJJJJJJJIIJJJJJJJJJJJJIIIJJJJJJKKLLMMMMMMLLLLLLLLMMMMMMMMMMMMMMMMMMNNNNNNNNNOOOOOOOOOPPOOPPPPPPPPOPQQQQQQQPPPPPQQQQQQQQQQQQQRRRRRRRRRRRRRQQQQQRRSSSTTTTUUTTTTTTTUUUUUUUUUUUUUUUUUUVVVVVVVVVVUUVVVVVVVVVVVWWWWWWWWWWWWWXXXXXXXXXXXXXYYXXXWWWXXYZZYYYYYYZZZZZZYYYZ[[[[[[ZZZ[\[ZZZ[[[\\\\\\\[\\]\\[\[[]^]]\\\\\\[[\\]\\[\\[[\]]\[[\\]^^\[\]^_____________`aa```````_____```aaaaaa``ba`abca__`bccdbbba[O@3--143.' "(*' !#!"" "! !#$$% %*)$"#&++*1>MZbggfgfbbceeddddcceeeeeeddcccbbccccccccccccdddddddddddddeeeeeeeeeeeeeddddddddddddcccccccccccccccccbbbbbbbbbcdddddddddddddcccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaabbaa`____```````aaaa``___`````````````^^^^^^^^^^^]]]]]]]]]]]]]]^^^]]]\\\[[[[[[[\\\]\\[\[[[\\\[[[[ZZZZZZZZZZZZ[[[[[ZYYYYYYYYYYYXXYXXXXXXXXXXWXXXXXWWWWVVVVWWWWWWWVVVVVVUVVVVVVVVVVVUUTUUVVVVVUUUUUUUUUTTTTTTTSSSSSRRRRRRRRSTTSSSSRRRQQQQQQQQQQQQQQQQQQQQQQQPPPPPPOOOOONNNNNNNMMNNNNNNNNNMMLLLLLLLLLLKKKMMMMMMLLLLLLLLLLLLLKKKKKKKJJJJJJJJJJJIJJJJIIIIIIIJIIIIIIIHHHHHHHHGGFFFFFFFFGGFFFFFGFFFEEEFFEEEEEEEEEEDEEEEEEEDDDDDDEEDDDDDDDDDEDBBCCCCCBBBBAACBBBBBBBBBBAABBBA@@@@@@@@???????????????@@@?>==>>??>=>>>>>>>====>=========<<<<<<<<<<<<<<<<<<<<<;;;;;;;<<<;;;;<<<<;;IIIIIIIIIHHHHIIIIJIIIIIIIIIIIIIIIIIIIJJJJJJJJJJJJKKIIJJKKJJJJKLLMMMMMMLLLLLLLLMMMMMMMMMMMMMMMMMMNNNNNNNNOONNNNOOOOOOOOPPPPPPPOOPQQQQQQQQQQPPPQQQQQQQQQQQQQRQRRRRRRRRSRRRRRRRSSSSTTTTUUTTTTTTTUUUUUUUUUUUUUUUUUUUUUUUUUUVVVVVVVVVVVVVVWWWWWWWWWWWWWWXXXXXXXXXXXXXYYXXXXWXXYYZZYYYYYYZZZZZZYYYZ[[[[[[ZZZ[[[ZZZ[[[\[[[[[[[[\\\[[[[[\]]\\\\\\\\\\\\\\\[[\\\\\\\\\]]^^]\\]^_____________`aa``````______```aaaaaa`_^_```_`abbbaaaa`^YN?1&"#(++'"#(*)% ""! !"#$ %('""'+*(+2<FQ]ehfeddddeedccdfeeeeeeeddccccdddddddddddddeeeeeeeeeeeeeeeeeeeeeeeeeeeedddddddddddddddddddddddddccccccccccdddddddddddddccccccccccccccbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaa````_```````aaaa`_____````````````_^^^^^^^^^^^^]]]]]]]]]]]]^^^^]]]\\[[[[[[[[[\\\\[[[[[[[[[[[ZZZZZZZZZZZZZ[[[[[ZZYYYYYYYYYYYYYYXXXXXXXXXXXXXXXWWWWWVVVWWWWWWWVVVVVVVVVVWWWWVVVVVUUUUUUUUUUUUUUUUUUTTTTTTTSSSSSSSRRRRRSSSSSSSRRRRRRRRRRRRRRRRRRRRQQQQQQPPPPPPONNNNNNNNNNNNMMNNNNNNNNNMMMMLLLLLLLLLLLMMMMMMLLLLLLLLLLLLLKKKKKKKKJJJJJJJJJJJJJJJIIIIIIIJIIHHHHIHHHHHHHGGGGFFFFFFFFFFFFFGGGFFFEEEFEEEEEEEEEEDEEEEEEEDDDDDDEEEDDDDDDDDDDBBCCCCCCBBBABCBBBBBBBBBAAABBAAA@@@@@@@@@@@@@@@@@@@@??@@@?>==>>??=========>>>>=====<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;;;;;;:::;;;;;::JJJJIIJIIIIIIIHIIIIIIIIHHIIIIIIIIIIJJJJJJJJJJJJJJKKJJJKLKKJJKKLLMMMMMMLLLLLLLMMMMMMMMMMNNMMMMNNNNNNNNNNOOOOOOOOPPOOOPPPPPPPPPPOPQQQQQQQQQQPPQQQQQQQQQQQQQQQQQRRRRRRSSSSRRRRSSSTTTTTTTTTSTTTTUUUUTTTTTTTTTTTTUUUUUUUUUUUVVVVVVVVVVVVVWWWWWWWWWWWWWWWXXXXXXXXXXXXXYXXXXXXXYYYYYYYYYYYZZZZZZYYYZZZZZZZ[ZZ[[[ZZZZZ[\[[[[[[[[[[[[[[\\\\\\\\\\\\\\\\\\\\\\\\\\\\]]]]^^^]]\]^_____________`a````````_____````aaaaa`_]_a_]]acb]XVTSUVSK=.""#"'+)$!!!!!"! !! "#$$"!')'#""%,8IV^`bfgedefecbdfeeeeeeeeddccdddddddddddddeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedddddddddddddddddddddeeededccccdddddddddddddddddcccccccccccccbbbbbbbbbbbbaaabaaaaaa```aaaaaaaaaaaa`````aaaa``_```aaaaaa```__`````````````___^^^^^^^^^^]]]]]]]]]]]]^^^^]]]]\[[\\\\\\\[[[[[[[[[[[[[[[[[[[[[ZZZZ[[ZZZZZ[ZZYYYYYYYYYYYYYYYYYXXXXXXXXXXXXWWWWWVVVWWWWWWWVVVVVVVVVVVWWWVVVVVUUUVVVVVVVUUUTTTTUTTTTTTTTTTTTTTSSSSSSSRRRSSSRRRRRRRRRRRRRRRRRRRQQQQQQQPPPPPPOOOOOOOONNNNNNNNOONNONNNNNNMMMMMMMMMMLMMMMMMLLLLLLLLLLLLLLKKKKKKKKJJJJJJJJJJJJJJIJJJJJIIIIIIIIIIIIHHHHGGGGGFFFFFFFFFGGGGGGGFFFEFFEEEEEEEEEEDEEEEEEEDDDDDDEEEEDDDDDDDDDCCCCCCBBCBBBBCBBBBBBBBBAAABBAAAA@@@@@@@@@@@@@@@@@@@??@@@?>==>>??=<<=====>>>>>===>====<<===============<<<<<<<<<<;;;;::::::;;::::JJJJIIIIIIIIIHHIIIIIIIIHHIIIIIIIIIIJJJJJJJJJJJJJKKKJJKLLKKKKKKLLLLLMMMMMMMMMMNNNNNNNNNNNNNNNNNNNNNOOOONNNOOOOOOOOOOPPPPPOOOPPPPPQQQQQQQQQQQQQRRRRRRRRRRRRRQQQQRRRRSSSTSSSSSSSTTTTTTTTTTTTTUTUUVUUTUUUUUUUUUTUUUUUUUUUUVVVVVVVVVVVVVWWWWWWWWWWWWWWWWXXXXXXXXXXXXXXXXXXXXYYYYYYYYYYYZZZZZZZZZZZZZ[[[[[Z[[[[ZZZ[[[[[[[[[[[[[[[[[[\\\\\\\\\\\\\\\]]]\\\\\\]]]]]]]^__^^]]^______________```````````````````aaaaaa``bc_]^dc\OEA=;ADD<1(! "$!!"&&"! !#" ! !"#!#&$!&4>GR]dgfcdggdbbceeeeeeeeeeeddeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddeeeeeeeeeeeeefeeeeddddeeeedddddddddddddddddcccccbbbbbbbbbbbbbbbbbaaaaaaaaa``aaaaaaaaaaaaa```a`aaaaa````aaaaaaaaa````````````````_____^^^^^^^^^^^^^^^^^^^^^_^^^]]]]\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZYYYYYYYYYYYYYYYYYYYXXXXXXXXXXWWWWWVWWWWWWWWVVVVVVVVVVWWWVVVVVUUVVVVVVVVUUUTTTTTTUTTTTTTUUUUUUUUTTTTRRRRRSSSSSSSSRRRRRRRRRRRRRQQQQQQQQPPPPPOOOOOOONNOOONNNONNNNNNONNNOONNNNNNNNNNMMMMMMMMLLLLLLLLLLLLLKKKKKKKKJJJJJJJJJJJJJJJJJJJIIIIIIIIIHHHHIIHGGGGGGGGGFFFFGGGGGGGGGFFFFFEEEEEEEEEEDEEEEEEEDDDDDDEEEEEDDDDDDDDCCCCCBBBBBCCCBBBBBBBBBBBAABBBBBBAAA@@@@@@@@@@@@@@@@??@@@?>==>>??=<<<====>>>>>========<<<<<<<<<<<<<<<<<=====<<<<<<<<;::::::;;;:::JJJIIIIIIIIIIHIIIIIIIIIIHIIIIIIIIJJJJJJJJJJJJJKKKKKKKLLMLKKKKLLLLLLMMMMMMMMMNNNNNNNNNNNNNNNNNNNNNNNNNNOOOOOOOOPPPPPQQPPPPOOPPPQQQQQQQQQQQQQQQRRRRRRRRRRRRRQQQQQRRSSSTTTTTTSSSTSTTTTTTTTTTTTUUUVVUUUUUUUUUUUUUUUUUUUUVVVVVVVVVVVVVWWWWWWWWWWWWWWWWWWXXXXXXXXXXXXXXXXXYYYYYYYYYYYYYZZZZZZYYZZZZZZ[[[[Z[[[[[ZZZ[Z[\\[\\\\[\\\\[\\\\\\\\\\]]]]]]]^]]]]]]]]]]^^]]]^___^^]^_______________`````````````````aaaa```accb_adf^N</+&$)--*$ !" "'(%! $# !$$! ! "$$#$#%(-9HU_dccdffdcdeeeeeeeffffeefffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeedddddeeeeeeeeeeeeefffeeeeeeeeeedddddddddddddeeddddccbbbbbbbbbbbbbbbbbbaaaabbaaa``aaaaaaaaaaaaaa````abaaaa``aaaaaaa`aa````_```````````________^^^^^_______________^^^^^]]]]]]^]]\\[\\\\[[\\\\\\\\\\\\\[Z[[[[[[[[[[ZZZZZYYYYYYYYYZZZZYYYYYYYYYYYXXXXXWWWWWWWWWWWWWWWVVVVVVWWWWWWWVVVVVVVVVVVVVVVUTTTTTTTTTTSTTUUUTUUTTTTUTSRSRSSSSSSSSSRRRRRRRRRRRRRQQQQQQQQQPPPPPPPPPOOOOOOOONNOOOOOOONNNOOOONNNNNNNNNMMMMMMMMMLLLLLLLLLLLLLKKKKKKKKKJJJJJJJJJJKKKKKKJIIIIIIIIIIIIIIIHGGGGHGHGGGGFFGGGGGGHGGGGFFFEEEEEEEEEEDEEEEEEEDDDDDDEEEEEEEEDDDDDDDDCCCCCCCCCCCBBBBBBBBBBAABABBBBAAAA@@@@@@@@@@@@@@@??@@@?>==>>??=<<=====>>>>>>>========================<<<<<<<<<<;;;;;;::;;;;;;;IIIIIIIIIIHHHIIJIIJJJIJIIIIIIIIIJJJJJJJJJJJJJKKKKKKKLLMMMLKKKLLLLLLMMMMMMMMMNOOOONNNNNNNNNNNNOOOOOOOOOOOOOOOPPPPPPPQQPPPOOOPPPQQQQQQQQQQQQRRRRSRRRRRRRRRSRQQQRRRSSSTTTTTTTTTTTTTUUUTTTTUUUUUVVVVUUUUUUUUUUUUUVVVVUUVVVVVVVVVVVVVWWWWWWWXXXXXXXXXXWXXXXXXXXXXXXXXXXXYYYYYYYYYYYYYZZZZZZZZZZ[[[[[[[Z[[[[\[[ZZZ[[[[[[[\\[[[[[[\\\]]\\\\\\]]]]]]]^^]]]]]]]]^^^]]^^_____^^^_____________``````````````````````a``ab`_`bc^O=, !!!"! "#" %(+*"#$""$"!! !!$%%$"""!%0@Q_efedccddeeeeeeefffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeeeeeededdeeeeeeeeeeeeefeeeeeeeeeeeeeeeeeeeeeeeeeeeedddccbbbbcccccccccccccaaabbbbbba``aaaaaaaaaaaaaaaaa`aaaaaaaaabbabbaa```aa`````````````_________^^^_________________^^^^^^]]]^^]]]\\[[[[\\\\\\\\\\\\\\\[[[[Z[[[[[[ZZZZZZZYYYYYYYYZZZZZYYYYYYYYYYYXXXXXWWWWWXXWWWWWWWWWWVVVWWWWWWWVVVVVVVVVVVVVVVVUUUUTUUUUTTTTTTTUUUUTUUTTTSSSSSSSRRRRRRRRRRRRRRRRRRQRQQQQQQQQQQPPPPPPPPPOOOOOOOOOOOOOOOOONNNNNNNNMMMMMMMMMMMMMMMLLLLMLLLLLLLLKLKKKKKKKJJJJJJJJJKKKKKKJIIIJJJJIJJJJIIIIGGGHHHHHGGGFFGHHHHHHGGGGFFFEEEEEEEEEEDEEEEEEEDDDDDDDEEEEEEEEDDDDEEDDDCCCCDDDCCBBBBBBBBBAAABBBCCCBBBA@@@@@@@@@@@@@@@??@@@?>==>>??>===========>>>===========================<====<<<<<<<;;;;<<<<;;IIIIHHHHHHHHIIIIIJJJJIIIIIIIIIIJJJJJJJJJJJJJKKKKKKKLLLLMMMLKKLLMLLMMMMMMMMNNNNNNNNNNONNNONNNONNNNNNOOOOOOOOOPPPPPPPQQPPPPOPPPPQQPPQQQQQQQRRRRSSSSSSSSSSSSRQQRRRRSSTTTTTTTSTTUUUUUUUTTTUUUVVVVVVVVUVVVVVVVVVVUUUUUUVVVVVVVVVVVVVWWWWWWWXXXXXXXXXXXXXYYYYYYYYYYYYYXXYYYYYYYYYYYZZZZZZZZZZ[[ZZZZZZZ[[[[\\[[[ZZZ[Z[\\[\\\\[\[[\\\\]]]\\\\\]]]]]]]^^^]]]]]]^^^^^^^_____`__^_```````````````````````````````_``aaa`___a`YL;+! ! #$#" " !'+*)& "! !!" ""! !$$! +>Tdjhdabdeeeeeeeefffffffffffffffffffffffffffffffffffffffffffffffeeeeeeeeeeffffffffffffffeeeddddddeeeeeeeeeeeeeeeeeeeeddcccbbbcccccccccccccbabbbbbbbaa`abbbbbbbbbbbbbbbaa`a`aaaabbaaaaaaa`aaaaaaaaaaaaaaaa````___________________________^^^^^^]]^^]]]]\\\\\\\\\\\\\\\\\\\\\[[[[ZZZZ[ZZZZZZZZZYYYYYYYZZZZZZYYYYYYYYYYYXXXXXWWWXXXXWWWWWWWWWVVVWWWWWWWVVVVVVVVVVVVVVVVUUUUTUUUUUUUTSSTUTTUUUUTTTTTTTSSSRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQPPPPPPPPOOOOONNOOOOOOOONNNMNNNMMMMMMMMNNNMMMMMMMMMMMMMLLLLLLLLLLKKKKKKJJJJJJJJJKKKKKKJJIIJJJJIIIIIIIIIHHHHHHHHHGGGGHHHHHHHGGGGGFFEEEEEEEEEEDEEEEEEEDDDDDDDDEEEEEEEEDCCEEDDDDCCCDDDDCBABBBBBBBAAABBCBBBBBAA@@@@@@@@@@@@@@@??@@@?>===>??>>>>======<<=>>=========<=================<<<<<<==<<;;;;;;<<<;;;HIHHHHHHHHHGHIIIIJJJJIIIIIIIIIJJJJJJJJJJJJJKKKKKKKKKKLLMMLLLLLLMMMMMMMMMMMNNNNNNNNNNONNNONNNONNNNNNOOOPOOOOOPPPPPPPQQPPPPPPPPPQQPPPQQQQQRRRRRRRRRRRRRRRRRRQRRRRSSSTTTTTTTTTTUTTTTUUUTTUUUVVVVVVUVVVVVVVVVVVVVUUUUVVVVVVVVVVVVVWWWWWWWWXXXXXXXXXXXXXYYYYYYYYYYYYYYYYYYYYYYYZZZZZZZZZZZZ[[[[[[[[Z[[[[[[\[[[ZZZ[[[[[[[[[[[[[\\\\]]]]]]]]]]]]]]]]^^^^^]]]^^^^^^___^^__``_^_``````````````````````````````````aaa`_abaXH6& !#""*/.'!!$" !"#""! *AZhkgdcdeeeeeeeeeffffggfggggggggggffffffffffffffffffffffffffffffffeeeeeeeffffffffffffffeeedddddddeeeeeeeeeeeeeeefeeeddcccbbbcccccccccccccbbbbbbbaabaaabbbbbbbbbbbbbbbbbaaaaaabbbaabaaaaaaaaaaaaaaaaaaaaa```````________________________^^^^^^^^^^]]]\\\\\\\\]]\\\\\\\\\\\[[[[[[Z[[ZZZZZZZZZZZYYYYYYZZZZZZYYYYYYYZYYYYYXXXXXXXXXXXWWWWWWWWVWWWWWWWWWWVVVVVVVVVVVVVVVUUUUUTTTTTUTSSSTTTUUUTTTTTUTTTTSSRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQPPPPPPPPOOOOONNOOOOOPOONNNNNNNMMMMMMMMNNNNMMMMMMMMMMMMMLLLLLLLLLKKKKKKKKJJJJJJJJJJJJJJJIIJJIIIIIIIIIIHHGHHHHHHHHHGGHHHHHHHGGGGFFFEEEEEEEEEEDEEEEEEEDDDDDDDDEEEFFFEEDCCEEDDDDDCCCCDDCBBBBBBBBBAAABCBBBBBBBAAA@@@@@@@@@@@@@??@@@?>===>??>>>>>===<<<<==========<<<================<<<<<<<=<<;::::::;;;:::IIIIIHHHHHHHHIIIJJJJJJJIIIIIIJJJJJJJJJJJJJKKKKKKKKKKKLLLLLLLLMMMMMMMMMMMMMMMMMMMMMNNOONOONNNNNNOOOOOOOOOOOOOOPPPPPPPPPQQQQQQQPPPPPPQQQQRRRRRSRRRRRRRRRRRRRQRRSSSSSSTTTSSTTTTTTTUUUUUUUUVVVVVVVUVVVVVVVVVVVVVVVUUVVVVVVVVVVVVVWWWWWWWWWXXXXXXXXXXXXXYYYYYYYYYYYYYYYYYYYYYYYZZZZZZZZZZZ[[[[ZZZ[Z[[[[[[[[[[[ZZZ[[[[[[[[[[[[\\\]]]]]]]]]]]]]]]]]]^^^^]]]]]^^^^^___^^__``_]^````````````___``````````````````aaaabcdaXH5% #)//) #))% "#$$" "" ! #&% ,DW`bbcfgeeeeeeeeeffffggggggggggggfffffffffffffffffffffffffffffffffeeeeeefffffffffffffffeeeeddeeeeeeeeeeeeeeeeeeeeeedddccccccccccccccccccbbbbbbaaaaaaabbbbbbbbbbbbbbbbbaaaaaabbbaaaaa`aaaaaaaaaaaaaaaaaaa``````_______________________^^^^^^^^^^]]]]]]]]]]\\]]]]]\\\\\\[\\\[[[[[[[ZZZZZZZZZZZZYYYYYZZZZYYYYYYYYYZZYYYYYXXXXXXXXXXXWWWWWWWVVWWWWWWWWWVVVVVVVVVVVVVVVUUVVUUUTTTUUSSSTUUUUUUUTTTTTTTTSSRSSSRRRRRRRRRRRRRRRRRRRRQQQQQQQPPPPPPPPOOOOONOOOOOOOOOOONMNNNMMMMMMMMMNNNNMMMMMMMMMMMMMMLLLLLLKKKKKKKKKKJJJJJJJIJJJJJIIIJJIIIIIIIIIIHHHHHHHHHHHHGGGGGHHHHGGGGFFFEEEEEEEEEEDEEEEEEEDDDDDDDEEEFFFFEEDDDEEDDDDDDCCCDCBBBBBBBBBBAAACCBBBAAAAAAA@@@@@@@@@@@@@??@@@?>==>>??>>>>======<<=========<<<================<<<<==<<<<<;::::::;;::::JJJJIIIIIIIIIIIIJJJJJJJIIIIIJJJJJJJJJKJJKKKKKKKKKKKKKKLLLLLLLMNNNNNNNMMMMMMMLLLLLMNNNOOOPONNNNNNNOOONNOOOOOOOOPPPPPPPPQQQQQQQPPPPPPQQQRRRRRSSRRRRRRRRRRRRRRRRSSSSSSSSSSSSSTTTTUUUUUUVUVVVVVVVUUVVVVVVVVVVVVVVUUVVVVVVVVVWVVVWWWWWWWWWWXXXXXXXXXXXXXYYYYYYYYYYYYYZZYYYXXXYYZ[[ZYZZYZZ[ZZZ[ZYZ[[[\\\[[[Z[[[ZZZ[Z[[[[[[[[\\\\]]]]]]]]]]]]]]]]]]]^^^]]]]]]^^^^^^__^^_`a`_]^````````````____``````aaaa````__```aabdbZJ6% #).1,"!*.,$"#""!! "'& -<HOV]dhfeeeeeddeeeefggggggggggggffffffffffffffffffffffffffffffffffffeeeffffffffffffffffeeeddeeeeeeeeeeeeeeeeeeeeeedddcccccccccccccccccccbbbbbaaaaabbbbbbbbbbbbbbbbbbcbbbbaaaaaaaa```````aaaaaaaaaaaaaaa``````````________________^_^^^^^^^^^^]]]]]]^^]]]]]]]]]]\\\\\[[[\\\[[[[ZZZZZZZZZZZZZZZYYYYZYYYYYYYYYYXYZZZYYYYXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWVVVVUUUUUUVVVVUUUUUUUTTTTTTSTTUVVVVVUUUTSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRQQQQQQQPPPPPPPPOOOOONNNNOOOONOOONNNNNNMMMMMMMNNNNNNMMMMMMMMMMMMMLLLLLLLKKKKKKKKKKJJJJJJIIIIIIIIIIIIIIIIIHHHHIIIHIHHGHHHHHHGFGGHHGGGGFFFFEEEEEEEEEEDEEEEEEEDDDDDDDDEFFFFFEEDDCDDDDDDDDDDCCCBBBBBBBBBBAAABCBBAAAAAAAA@@@@@@@@@@@@@??@@@?>===>??>>>===============<<<<<<<<==========>==<<<<<<<<;;<<;;;:::;;;;;::JJJIIIIIIIIIIIIIJJJJJJJIIIIJJJJJJJJJKKKJKKKKKKKKKKLKJKLLLLLLLMMMMNNNNNMMMMMMMLLLMMNNNNOOOONNNNNNNONNNNNOOOOOOOOOOPPPPPQQQQQQQPPPPPPQQQRRRRSSSRRRRRRRRRRRRRSSSSSSSSSSSSSSSSTTTTUUUUUUUUVVVVVVVUUVVVVVVVVVVVVVVVVVVVVVVVVVWWVWWWWWWWWWWXXXXXXXXXXXXXXYYYYYYYYYYYYYYZZYYXXXYYZZZZZZZZZZ[[[[[ZZZ[[\\\\[[ZZ[[[ZZZZZ[[[\\[[[\\\]]]]]]]^^^]]]]]]]\\]^^^]]]]]]^^^^]^_^^^_```_]^````````````____`_``````aa`__````_``acc\M:&! !!!#&,/.&!(..( !#"$%$,5AMYbefeeddddeeefffggggfffggggffffffffffffffffffffffffffffffffffffffffffffffffffffgfffeeeeeeefeeeeeeeeeeeeeeeeeedddccccccccccccccccccccbbbaaaaaabbbbbbbbbbbbbbbbbccccbbaa``abba```````a``````````````a```````````_____________^^^^^^^^^^^^^]\]]]^^^^]]]]]]]]]\\\\[[[[\\\\[[[Z[ZZZZZZZZZZZZZZZYYZYYYYYYYYYYXYZZZZYYYXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWVVVVUVVUUVVVVUUUUUUUTUUTTTTTUUVVVVVVUTSSSSSSSTTTTTSSSSSSSSSSSSSSRRRRRRQQQQQQQPPPPPPOOOOOOOOOOOOPOONNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMLLLLLLLLKKKKKKKKKKJJJJJIIHHHIIIIIIIIIIIHHIIIIIIIIHHHHHHHHHGGGHHHGGGFFFFFEEEEEEEEEEDEEEEEEEDDDDDDDDEFFFFFEEDDCCDDDDDDDDDCCBCBBBBBBBBBBABBCBBAAAAAAAAAAAAAAAAAAAAA@?@@@?>==>>??>=========>============<<<=============<<<<=<<<<<<;;<<;;;;<<<<;;IIIIIIIIIIIIIIIIIIIIIIIIIIIJJJKKKKKKKKKJJKKLLLLLLLLLLLLLLLLLLLLLLLMMMNNMMMMMMMMMMNNNNNOOONNNNNNNNNNONNOOOOOOOPPPPPPPPQQQQQQQQQQQQQQQQRRRSSSSSSSSRRRRRRRSSRRSSSSSTTTTTTTSTTTTTUUUUUUUUUUVVVVVVUUUUVVVVVVVVVVVVWWWWWWWWWWWWWWWWWWWXXXXXXWWWWXXXXXXYYYYYYYYYYYYYYYYXXXYYYYYYZZZZZZZZZZ[[[[[[[Z[[[[[[[[[[ZZZZYYYZZ[\]^^\\\\\\]]]]]]]]]]\\\\\]]^^^^^^^^^^_________^^^__`_^^_aa``````____^^__^]^__^^`bc`\]`cda^^`dgfR9% !!!!#"#(+-+&&.-%#$ %$ !%.>P^hjebadfefgihdefhgfeegggffffeeeeeeffgfffffffffffffffffffffffffeeeeeeeeeeeeeeefffffffffffeeeeeeeeeeeeeeddddddddddddcccccccccccccbbbbbaaaa`aaabbbbbaaaaabbbbabbbbbbbaaa`aaaaaaa```aaaaaaaaa`````````````________`____`____^^_^^^^^^^^]]]^^^]]^^^^^]]\]]]]]]]\\\\\\\\\\\\\[[[[[[ZZZZZZZZZYYYYZZZZZYYYYYYYYZZYYXXXXXYYYXXXXXXXXXXXWWVVVWWWWWWWVVVUVVVVVVVUUUUUUUUUUUUUUUUUTTTUUUUUVUVUUUUTTTTTTTTTSSSSTTTTSSSSTTTRRRRRQQQPPPPPPPPPPPPPOOOOOOOOOOOONONNNNNNNNNNMMNNNNNNNNNNMMMMMMMMMMMMMMMMMLLLLLLLLLKKKKKKJIIIIIIJJJJJJJIIIIIIHHIIIIIIIIIHHHHHHHGFFGGGGGGGGGGFFFFFFFEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDCDDDDDDDDDCCCBBBBBCBBBBBBBBBBBBBBAAAAAAAA@@@@@@@@@@?@@??????????>??>>>>>>>>>>>>>>>>>==============<====<<<<<<<<<<<;;;;;;;;;<<<<;;JJIIIIIIIIIIIIIIIIIIIIIIIIIJJJKKKKKKKKKJKKKLLLLLLLLLMMLLLLLLLLLLLLMMMNNNNNNNNNNNNNNNOONNNNNNOOOOOOONOOOOOOOPPPPPPPPQQQQQQQQQQQQRQQQQQRRRSSSSSSSSRRRRRRRSSSRSSSSSTTTTTUUTTTTTTTTTTTTTTTUVVVVVVUUVVVVVVVVVVVVWWWWWWWWWWWWWWWWWWWWWXXXXXXXWWXXXXXXXYYYYYYYYYYYYYYYYXXXYYZZZZZZYZ[[[[[[[\\\\\[[[[[\\\\[[[[ZZZZZZ[[[\]]]]\\\\\]]]]]]]]]]]]]]]]]^^^^^^^^^___________^_____^^`a````````____`a`^^_``_^`ba`^`aba`abbb_VB, ## %(**)&" #(+( !" !# &6IZeggeffefggfeeeeeddfggfeefffffffffggggffffffffffffffffffffffffeeeeeeeeeeeeeffffffffffffeeeeeeeeeeeeeeedddddddddddddddccccccccccbbbbbaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbaaaaaaabaaa`a``````aa```_`````````_______````````___^___^^^^^^^^]]^^^^]^^^^^]]\]]]]]]]\\\\\\\\\\\\\[[\\\[[ZZZZZZZZZZYZ[[[[ZZZYYYYYYZZYYXXXXXYYYYXXXXXXXXXXXWVVVWWWWWWWWVVVVVVVVVVVVUUUUUUUUUVVVUUUUTTTTTUUUUUUVVUUUUUTTTTTSSSSTTTTSSSSTTTRRRRRRQQQPPPPQQPPPPPPPPPPPOOOOOOOONOOOOOOOOONNNNMMMMMMNNNMMMMMMMNMMMMMMMMMMLLLLLLLLKKKKKKJJJJJJJJJJJJJJIIIIIIHHHHHHHHHHHHHGHHHHGFGGGGGGGGGGFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDCCCCBBBCBBABBBBBBBBBBBAAAAAAAA@@@@@@@@@??@@?????????>>??>>>>>>>>>>=>>>>>>>>===>=========<<<<<<<;<<<<<<<<;;;;;;;;<<<<<<JJJIIIIIIIIIIIIIIIIIIIIIIIIJJJKKKKKKKKKJKKKKLLLLLLLMMMMMMMMMMMMMLLMMMNNNNNNNNNNNNNNNONNNNOOONNNNNNNOOOOOOOPPPPPPPPPQQQQQQQQQQQRRQQQQQRRRSSSSSSSSRRRRRRRSSSSSSSSSTTTTTUUUUUUUUTTUUUUTTUUUVVVVVUUVVVVVVVVVVVWWWWWWWWWWWWWWWWWWWWWWXXXXXXXXXXXXXXXXYYYYYYYYYYYYYYYYXXYYYYZZZZZZZ[[[Z[[[\[[[[\\[[[\\\\[[[\[[[[[\\\\\\\\\\\\\\]]]]]]]]]]]]]^^]^^^^^^^^^^___________________`a`_`````____aba`__`bb`_`a_`bcb_^`cc^WK>/# $$!%,,*&" #&&$ !"%#!!# .@R]ceeeefgeeggfeddfgggfefgggggggggggggfgggggggggggggggffffffffffffffffffffffgffffffffffffffffffffffffeeeedddddddddddddcccccccccbbbcbbbbbbbbbbbbbcbbbbbbbbbbbbbbbbbbaaaaaaaaabaaaaaaaaaaaa``````````````_____`````aa````________^^^^^]^^^^]^^^^^]\\]]]]]]]\\\\\\\\\\\\\[[[[[[\[[[[[[[[[[ZZ[[[[ZZZYYYYYYYZYYYYYYYYYYYYXXXXXXXXXXXWVWWWWWWWWWWVWWVVVVVVVVVVVVVVUUUUUUVVVUUUUUUUUUUUUUUUUUUUTTTTTTTSSTTTTTTTTTTSRRRRRRQQQQPPQQQPOOOPPQQQPPOOOOOOOOONNNOOOOONOOONNNNNNNNNNNNMMMMMNMMMMMMMMMMLLLLLLLLKKKKKKKJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHGGGGGHGGGGGFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDCDCCBCCCCBBBBBBBBBBBBAAAAAAAAA@@@@@@@@@??@@??????????>??>>>>>>>>>=>>>>>>>>>>=====>===========<<<<<<<<<<<<;;;;;;;;;;;;;JJJJIIIIIIIIIIIIIIIIIIIIIIIJJJKKKKKKKKKKKKKKKLLLMMMMMMMMMMMMMMMMLLMMMNNNNNNNNNNNNNNNOOOOOOOOOOOOONOOOOPOOPPPPPPPPPPQQQQQQQQQQRRRQQQRRRRRSSSSSSSSRRRRRRSSSSSSSSSSTTTTUUUVVVUUUUUUUUUUUUVUUVVVVUUVVVVVVVVVVWWWWVVVVVVWWWWWWWWWWWXXXXXXXXXXXXXXXXXYYYYYYYYYYYYYYYYYYYYYYYYZZZ[[[[[[Z[[[[[[[[[[[[[\\\\[[[[[[[\\]]]]\\[[\\\\\]]]]]]]]]]]]]^^^^^^^^^^^^^___________`_______``````````````aa`^^_abba__``bbb`^^_a\PC5) $'$!(.,'"! !%'#!!## $%! "&"$4CPX_bdghgghhgggghhggggggggggggggggggfggggggggggggggggggggfffffffffffffffffgggffffffffffffffffffffffeeeeedddddddddddddcccccccccccccccbbbbbbbbccccccccbbbbbbbbbbbbbabbbbbbbbbbaaaaaaaaaaaaa`````````````____````aaaaaaa``````___^^^^^^^^^]^^^^^]]\]]]]]]]]]\\\\\\\\\\\\\[[[\\\\\[[[[[[[[[[[[[ZZZZZYYYYYZZZZYYYYYXXYYYXXXXXXXXXXWWWWWXXXWWWWWWWWVVVVVVVVVVVVVUUUUUUVVVVUVVVVVUUUUTUUVVUUUTTTTTTTTTTTTUUUUTTSSSSSRRRRRQQQQRRQPPOPPQQQQPPOOPPOOOOOONOOOOOOONOOOOOOOONNNNNNNNNNNNMMMMMMMMMMLLLLLLLLLLKKKKKJJJJJJJJIIIIIIIIIIIIIHIIIIIIIIIIHHHHHHHHHHHHHGGFFFFGGGFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEDDDDDDDDEEEEEDDDDCCCCCCCCBBBBBBBBBBBBAAAAAAAA@@@@@@@@@@?@@??????????>??>>>>>>>>>=>??>>>>>>>>>=>>>>==========<=<<<<<<<<<<<<<<<<;;;;;;;JJJJJIIIIIIIIIIIIIIIIIIIIIIJJJKKKKKKKKKKKKKKKKKLMMMMMMMMMMMMMMMMLLMMMNNNNNNNNNNNNNNNOOOOOOOOOOONNOOOOOPPPPPPPPQQQQQQQQQQQQQRRRRRRRRRRRRSSSSSSSSSSRRRRSSSSSSSSSSTTTTTTTVVUUUUUUUTTTUUUUUUUVVVVVVVVVVVVVVVWWWWWVUVVVVWWWWWXWWWWXXXXXXXXXXYYYYYYYYYYYYYZZZZZZZZZZZZZZZYYYYZZZ[[[ZZ[[[[[[[[[[[[[\\\\\\\\[[[[\]]]]]]]\[[\\\]]]]]]]]]]]]]^^^^_^^^^^^^^^___________```__^^^_``````````````__^^^_`aa_^^_bb``a`^XRF9-$#'(#%('&$ !! "&%! #$" %"#' "+6ALW`ghfdffgghhihggghhfggghhhhhhgggggggggggggggggggggggggfffffffffffffffffggffffffffffffffffffffffeeeeeeeedddddddddddddccccccccdddddccbbbcccddddddcccbbccccccbbbbabbbbbbaaaaaaaaaaaaaaaa``````````````_`___```aaaaaa```a```____^^^^^^^^^^_^^]]\]]]]]]]]]]\\\\\\\\\\\\\[[\\\\\\[[[[[[\[ZZZZZZZZZZZZYYZZZZZZYYYXXYYYYXXXXXXXXXXXXXXXXXWWWWWXWWWWVVVVVVWWWWVVVUUVVVWWWVUUUUUUVUUTUUVUUTTTTTTTTTTSSSTTUUUTTRRSSSSSSRRRRRRRQPPPPPQRRQQPPPPPPOOOOOOOOOOOOOOOOOONONNOONNNNNNNNNNNMMMMMMMMMMLLLLLLLLLLLKKKKKKKKJJJJIIIIIIIIIIIIIIHHHHHHIIIIIHHHHHHHHHHHHGGFFFFGGGGGFFFFFFFEEEEEEEEEEEEEFFEEEEEEEEEDDEEEEEEEEDDDDCBBBBBBBBBBAAAAAAABAAAAAAAAA@@@@@@@@@@?@@??????????>??>>>>>>>>>=>???>>>>>>>>>>>>>>>>=======<=<<<;<<<<<<<<<<<<;;;;;;;JJJJJJIIIIIIIIIIIIIIIIIIIIIJJJKKKKKKKKKKKKKKKKLLLMNMMMMMMMMMMMMMLLMMMNNNNNNNNNNNNNNNONNNNNNNNNNNOOOOOOPPPPPQQQQQQQQQQQQQQQQRRRRRRRRRRRSSSSSSSSSSSSRRRSSSSSSSSSSTTTTTTTVVVVUUUUUUUUTUVUUUUVVVVVVVVVVVVVVWWWWWVUUVVVVWWWWXXXXWXXXXXXXXXXYYYYYYYYYYYYYYZZZZZZZZZZZZZZZYYYYZZ[[[[[[[[\[[[[\\\\[[\\\\\\\\[[\\\]]]]]]]]\\]]]]]]]]]]]^]]]]^^^__^^^^^^^^___________`````__^__``````___``````____```_____`]ZZ[[TG:-% $((% !(&" $'!!" !$"&"%*5EXejgddefffghhgggihgggghhhhhgggghhhhhhhhhhhhhhhhggggggggggggggggggggggggggfffffffffffffffffffffeeeeeeeeedddddddddddddddddddcddddddcccccccddddddcccccccccccccbbbbbbbbbbbaaaaaaaabbaaaaaaaaaaaa````````_^___`````````a`````___^^^__^^^^^__^^]\]^]]]]]]]]]]]]]]\\\\\\\\\\\\\\\[[[[[[[[ZZZZ[ZZZZZZZYYZZZZZZZYYXXYYYYYXXXXXXXXXXXXXXXXWWWWWXXWWWVVVVVVWWWWVVVUUVVVWWWVVVUUUUUUUUTUVUUUUTUTTTTTTSSSTTUUUTTRRSSSSSSSRRRRRRQPPQQQRSRQQPPPPPPPPPOOOOOPPPPOOOOPOOOOONNNOONNNNNNNNMMMMMMMMMMLLLLLLLLLLLLKKKKKKKJJJJIIIIJJJIIIIIJIIIIIIIIHHHHHHHHHHHHHHHHHGFFFFGGGGGGFFFFFFEEEEEEEEEEEEEFFEEEEEEEEEEDEEEEEEEEDDDDCCCCCBCCCCBBBBBBBBBAAAAAAAAA@@@@@@@@@??@@?????????>>??>>>>>>>>>=>???>>>>>>>>>>>>>>>>>>>>>>=<===<<;;;;;;;;;;<<;;;;;;;JJJJJJJIIIIIIIIIIIIIIIIIIIIJJJKKKKKKKKLLKKKKKLLLMMNMMMMMMMMMMMMMLLMMMNNNNNNNNNNNNNNNOOOOOOOOOOOOOPOOOOPPPPPQQQQQQQQQQQQQQQRRRRRRRRRRRSSSSSSSSTSSSSSSSSSSTSSSSTTTTTUTTTVVVVUUUUUUUUUUVVUUUVVVVVVVVVVVVVWWWWWWWVVVVVVWWWXXXXXXXXXXXXXYYYYYYYYYYYYYYYYZZZZZZZZZZZZZZZZZZZZZZ[[[[[[[[[[[[\\\\\[[\\\\\\\\[[\\\\\]]]]]^^^]]]]]]]]]]^^^]]]^^_____^^^__________``___````_____``````____```aaaaaaa```abb`ZTPONKA2% !! !! %*+(#"'&! ! ''!$#$$ #3I\ghgeffdcdfhhgghhfghgggggggffggggggggggggggggggggfffffffffffffffffffffffgffffffffffffffffffffffffeeeeeeeeeeeedddddddddddddddeddddccccccdddddddcccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaa`aa`````_^^___``````_```````________^^^^^___^]\^^^^]]]]]]]]]]]]]\\\\\\\\\[[[[[[[[ZZZZZ[[[[[[[ZZZZZYYYZZZZZZZYXXYYYYYYXXXXXXXXXXXXWWWWWWWWXWWWWVVVVVVWWWWVVVVVVVVWWWVVVUUUUUUUUUUUUVUUTTUTTTTTSSSTTUUUTTRRSSSSSSSSRRRRRQPPQRRSSRQPOPPPPPPPPPPPOPPPPPOOOOOOPPOONONNNNNNNNNNNMMMMMMMMMMLLMMMLLLLLLLLLKKKKKJJJJIIIIJJJJIIIIIIIIIIIIIHHHHHIIIHHHHHHHHGGFFFFGGGGGGFFFFFFEEEEEEEEEEEEEFFEEEEEEEEEEDEEEEEEEEDDDDCCCCCBBBBBBBBBBBBBBBAAAAAAAA@@@@@@@@@@?@@?????????>>??>>>>>>>>>>>>>>>>>>>>==========>>>>>>=<<<<=<<<<<<<<<<<<<;;;;;;;JJJJJJJJIIIIIIIIIIIIIIIIIIIJJJKKKKKKKKLLLLLLLLLMMMNMMMMMMMMMMMMMLLMMMNNNNNNNNNNNNNNNOOOOOOOOOOOOOOOOOOPQQQQQQQQQRRRQQQQQRRRRRRRRSSSSSSSSSSSSSTSSSSSSSSSSTSSSSTTTTTUUUUUVVUUUUUUUUUUVVUUUUVVVVVWWVVVVWWWWWWWWWVVVWWWWWXXXXXXXXXXXXXYYYYYZZZZZYYYYYYYZ[[[[[[[[[[[ZZZZZZZZ[[[[[[[[[[[[[[\\\\\[\\\\\\\\\\[\\\\\\\\]]^__^]]]]]]]^^^^^^^^^^________________````___`````___`````______````aaaba``aceee_SIC>94+# #"! !! )-,'"!%((" !$" $)& "&&"%6IZfkijifdcfijhfggfggggggggggggggggggggggggggggghgggggggggggggggggggggggggggffffffffffffffffffffffffeeeeeeeeeeeeeddddddddddddddddddccdddddddddddddcccdddcccbbbccccbbbbbbbbbbbbbaaaaaaaaaaaaa`aaaa``a`_____````____`````_________^^^^^___^]]^^^^^]]]]]]]]]]]]]\\\\\\\\[[[[[ZZ[[[[[[[[[[[[[[[[[[ZZZZZZZZZZYYYYYYYYYYYXXXXXYXXXXWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUVUUUUUTTTTTTTTTUUUUTTSSSSSSSSRRRRRRQPQQRRRRRQQPOPQQQPPPPPPPPPPPOOOOOOOOOOOOOONNNNNNNNNNNMMMMMMMMMMLLMMMMLLLLLLLLKKKKKJJJJJJJIJJJJJIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHGFFFGGGFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEDDDDDDDEEEEEDDDDCCCCCCBBBBBBBBBBBBBBAAAAAAAA@@@@@@@@@??@@?????????>>??>>>>>>>>>==>>>>>=================>>>=<<<<<==<<<<<<<<<<<;;;;;;;JJJJJJJJJJIIIIIIIIIIIIIIIIIJJJKKKKKKKKKKKLLLLLMMMMMMMMMMMMMMMMMMLLMMMMNNNNNNNNNNNNNNOOOOOOONOOOOOOOOOOPQQQQQQQQRRRRQQQQRRRRRRRRRSSSSSSSSSSSSSTTSSSSSSSSSTSSSSTTTTTUUTTUUUUVVUUUUUUUVVVVUUVVVVWWWVVVWWWWWWWWWWWWWXXXXXXXXXXXXXXXXXYYYYYYZZZZZZYYYYYYZ[[[[[[[[[[[ZZZZZZZ[[[[[[[[[[[[[\\\\\\\[\\\\\\\\\\[\\\\\\\]]]^__^]]]]]]^^^^^^^_^^^_______________`````__``````__``````______``aaa``aa``adeb]TG=6/(" #%#! %-/,&"!#%,.'%&&#!" "$#&'""$#%0@R_eikifehjjhfgggghggggggfffghgggggggggggggghggggggggggggggggggggggfgggggggffffffffffffffffffffffffeeeeeeeeeeeeeeedddddeedddddddddddddddccddddddcddddddccccbcccccbbbbbbbbbbbbbaaaabbbbbbbaa`aaaaaa`_____````___________________^^^____^^]^^^^^^]]]]]]]]]]]]]\\\\\\\[[[[[[[Z[[[[[[[[[[[[[[[[[ZZZZZZZZZZZYYYYYYYYYYYXXXXYYXXWVVVVVVVWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUVVVVVUUUUUUUUTUTTSSSTTTTTTTTTSSRRRRRRRRRRRRQQQRSRRQPPPPPQQQQQPPPPPPPPPOOOOOOOOOOONNNNNMNNNMMMMMNMMMMMMMMMMLLMMMMMLLLLLLLKKKKKJJJJJJJJJJJJJJJIIIIIIIIIJIIIHHHHIHHHHHHHHHHHGGFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEDEDDDDDDDDDEEEEDDDCCCCCCCCCBBBBCBBBBBBAAAAAAAAA@@@@@@@@@??@@?????????>>??>>>>>>>>>>=>>>>==============>====>>=<<<<<=<<<<<<<<<<<<;;;;;::KJJJJJJJJJJIIIIIIIIIIIIIIIIJJJKKKKKKKKKKKKLLLMMMMMMMMMMMMMMMMMMMLLMMMNNNNNNNNMNNNNNNONNNNNOOOOOOOOOOOPQQQQQQQQRRRRRQQQRRRRRRRRRRSSSSSSSSSSSSSTTTSSSSSSSTTSSTTTTTTUUUUUUTTUUUUUUUUUUVWVVUUUUVVWWWVVVWWWWWWWWWWWWXXXXXXXXXXXXXXXXXYYYYYYYZZZZZZZZZYYYZ[[[[[[[[[[[ZZZZZ[[[[[[[[[[[[[[[\\\\\\\\\\\\\\\\\\\\\\\\\\]]]]^^]]]]]]^^^^^^^___^^^^^___``_______`````````_```````__``______``ab```aa```c`YNB70-'!!!"&'$ "*/0+%"!"$&(.1,+,*$$&#!%%" !%!!! (9KXbhihfhiihghhhhhggggggggggggggggggggggggggggfggggffggggggggggggggggggggggffffffffffffffffffffffffffeeeeeeeeeeeeedddeeeddddddddddddddccccdddddcdddddddcccccccccccbbbbbbbbbbbbbaaaaaaaaaaaa`````a``````````____^^^______```___^^_____^]]^^^^^^^]]]]]]]]]]]]]\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[ZZZYYYYYZZZYYYYYYYYYYXXXYXXWVVVVVVVVVVVVVVVVVVVUUUUVVVVVVVVVUUUUUUUUUVVVVVVVUUUUVUUUUTTTSSTTTTSSSSTTTRRRRRRRRRRRRRQRSSSRPOOOPQQQQQQQPPPPPPPPOOOOOOOONNONNNNNNNMMMMMMMNMMMMMMMMMMLLMMMMMMLLLLLLKKKKKJJKKKKKKJJJJJJJIIIIIIIIIIJJIHHHHIHGGHHHHHHHHHGGFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDCCCCCCCCCCBBBBCBBBBBBAAAAAAAAA@@@@@@@@@??@@?????????>>??>>>>>>>>>>===========<<<<<<<===========<=======<<<<<<<<;;;;;::KKJJJJJJJJJJIIIIIIIIIIIIIIIJJJKKKKKKKKKKKKLMMMMMMMMMNNNNNNNNNNNMLLMMMNNNNNNNNNNNNNNNOOOOOOOOOOOPPPPOPPQQQQQQQRRRRRRQQRRRRRRRRRRSSSSSSSSSSSSSSTTSSSSSSSSTTSSSTTTTTUUUUUUUUUUUUUUUUUUVVVVUVVVVWWWWVWWWWWWWWWWXXXXXXXXXXXXXXXXXXXXXYYYYYYZ[[ZZZZZZZYYYZ[ZZZZZZZZZ[ZZZZZ[[[[[[[[[[[[[[[\\\\\\\\\\\\\\\\\\\\\\\\\\]]]]]]]]]]]]^^^^^^^___^^^^^__``________``````a`___``a``___``______``aaaaaba````YL=0(&'&# $((% !).0/*%""%'*,/0/..*#!&($"$" !!"" "!&5ET`gifgggggjihhhggggfggggggggggggggggggggfgggggggfffggggggggggfgggfgggggggfffgggggggggggggfffffffffffffeeeeeeeeeeeeeeedddccddddddddccccccddddddddddddcccccccccccbbbbbbbbbbbbbaaaaaaaaaaabaaaaaaaaaaaaaa````_^^^^_____````___^^_____^^]^^^^^^^]]]]]]]]]]]]]\\\\\\\\\[[[[[[[[[[ZZZZZZZZZZZZ[[[ZZYYYYYZZZZZYYYYYYYYYXXXXXXWVVVWVVVVVVVVVVVVUUUUUUUVVVVVVVVUUUUUUUUVVVVVVVVVUUUUUTTUUTSSSTTTTSSSSTTTRRRRRRRRRRRRRRSSRRPNMMNPQQQQQQQQPPPPPPPOPOOOOOOOOONNNNNNNMMMMMMMNMMMMMMMMMMLLMMMMMMLLLLLLKJJJJJKKKKKKKJJJJJJJIIIIIIIIIIIIIHIIIIHGGHHHHHHHHHHGFFFFFFEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDCDDDDDDDDCCCCCCCCCCCCBBBBBBBBBBBBAAAAAAAA@@@@@@@@@??@@?????????>>??>>>>>>>>>>========<<<<<<<<<<<<========<<<<<<<<<<=<==<<<;;;;:::JKKKKLLLKKKJJJJJJJJIIIIIIJJJJJKKKKLLLLLLLLLMMMMNNNMMMMMMMMNNNNMNOONNNNNNNNNNOOONNNNNOOOOPPOOOOPPPPQQQQQRQQQQRSRRQQPQRRRRRRRRRRRRSSSSSSSTTTTTSSSTTTTTTTTTTSTTTTSTTUTTUUUTTTTTUUUUUUUVVVUUVVVWWWWWWWWWWWWWWWWWWWXXXXXXXXXXXXXXYYYYYYYYYYYZZZZZZZZZZZZZZ[[[[[[[ZZZZZZZZ[[[[[[[[\\\\\\\\\\\\\\[\\\\\\\\\\\]^]]]\\\]]]^^]]^__^^^^^^^________________``````````a````````````aaa````````aa`ab`_`bc]O=/&% ! !"#$%&$ $-320.)$#).00//,&'+'# $%## "!!!#,?Uhkdfhhffiiggggggfffgggggfggggggghhhggggggggggggfffffffffffffgggfffffffggffggggggggggggggffffffffffffffffffffffeeeeeeeeeeeeeeedddddddddddddddddddcccccccbbbbbbbbbbbbbaaaabaaaaaaaaaaaaaaaaaaaaaa`a````````____```````____``````___^^^__^^^^]]]\\]]]\\\\]]]]]\\\\\\\[[[[[[[[[[[[[[[[[[[[ZZZZZYYZZZYYY[ZXWYZZYXXXWWXXX[ZWVWZYVWXYWUVVWWWUUVWVUVWWXXVTSWWUTUUUTTUVUUUTUWWVUUVTSTUWSQTTSTUTSSUUTSRQSTSQPQRSSRQSUSPMKHGGHJKLOQPPPPPQQQQQQPPPPOOOOOOPPPONNNNNNNNMMMMNMMMNNONLKKLLMMMMMMLLLLLLKKKKKKKKKKKKJJIIIIIJJJJJJJJJIIIIIIIIIIHHHHHHHHHGGGGFFFFFFFFFFFFEEFFFFEEEEEEEEEEEEEEDDDDDDDCDDDDDDDDDDCCCCCCCBBBBBBBBBBBBBBBBBBAA@??????@@@@@@@@??????????>>>>>>>>==>==============<<<<<<<<<<<<<<==<<<<<<<<<<<<<<<<<;;;;;;;KKKKKLLLKKKKKJJJJJJJJJJIIJJJJJKKKKLLLLLLLLLLMMMNNNNMMMMMMMNNNNNNOOOOONNNNNOOOOOOONONOOPOPPOOPPPPPQQQQQRRQQQRRSSRRQQRRRRRRRRRRRRRSSSSSSSTTTTTTSSTTTTTTTTTTTTSTTTTTUTTTTTTTUUUUUUUUUUUUUVVVVVWWWWWXXWWWWWWWWWWWWXXXXXXXXXXXXXYYYYYYYYYYYYZZZZZZZ[[[[ZZZZ[[[[[[[[[[[[[[[[[[[[[[\\\\\\\\\\\\\\\\\\\\\\\\\]^^]]]]\\]]]^^]]]^^^^]]^^^________________`````````aaaaaaaaaaaaaaaa``___````aa``ba`bdcYG5(!" #')))('# .431/.,)&$&*-./.,(#!*23+""&'&#! 0Jcjfgijihhhgggggggggggggggggggggghhhhhhhghhhhhhggggggggggggggggggggggggggggggggggggggggggggggffffffffffffffffffeeeeeeeeeeeeeeedddddddddddddddddddcccccccccccccccccccbbbbbbbbbbbbbbbaaaaaaabaaaaa`a``_`````````````````___``````____^___^^^^]]]]\\]]\\\\]]]]]]\\\\\\[[[ZZZZZ[[[[[[[[[[[[ZYYZZZZYYYYYY[ZVVWYYXXYYYYZYXYXTTVZZVUUVVWXVTTUVVVVUVWVTUWWVUXXUUUWXWVUUTTUVUUUTTUVUTTVXUSUTRSTTSSTUTTRQRTTSRQPQPPOONNMLLLLLMMNNOQQQQQQQQQQQQPPPPOOOOOPPQPNNNNNNNNNMMMNONMNNOONLKKLMMMMMMMLLLLLLLLKKKKKKKKKKKJIIJJJJJJJJJJJJIIIIIIIIIIIIIIIHHHGGGGGGFFFFFFFFFFFFFFFFFEEEEEEEFEEEEEEEEEEDDDDDDDDEEEDDDDDCCCCCCCCCCCCBBBABBBBBBBBA@??????@@@@@@@@@??????????>>>>>>>>>>==>>===========================<=======<;;<<<<<;;;;;;;KKKKKKKKKKKKKJJJJJJJJJJIIJJJJJKKKLLLLLLLLLLMMMMNNNNMMMMMMMNNNNMNNNNNNNNNNNOONNNOOOOOOOOOOOPOPPPPPPQQQQRRQQQRRSSSRRQRRRRRRRRRRRRRSSSSSSSTTTTTTTTTTTTTTTTTUTTTTUUUUUUUUUUUUUUUUVVVVVVUUVVVVVVWWWWWXXXXXXXXXXXXXXXXXXXXXXXXYYYYYYYYYYYYYZZZZZZZZZ[[[[[[[[[[[ZZ[[[[[[[[[[[[\\\\\\\\\\\\\\\\\\\\\\\\\\\\\]]^^]]]]\\]]]^^]]]^^^^]]]^_________________``````````aaaaaaaaaaaaa```__^^__`abb``a`acd_R?.$ #'+./-*(# )340/.//.+))*++//($#%+275,$ %(&# !! (@\jkiiijjihhhhhhhhhhhhhhggggggghhhhhhhhhgggggggggggggggggggghgffffffffffffffffffffffffffffffgffffffffffffffffffffeeeeeeeeeeeeedddddddddddddddddddcccccccccccccccccccbbbbbbbbbbbbbbbbaabbbbbbbbbaaa````aaaaaaa``````````__``````___^^___^^^^]]]]]]]]\\\\]]]]]]\\\\\\[[[[ZZZ[[[[[[[[[[[[[ZYZZZZYYXXYZZ\ZXWWYYXYZ[ZZ[ZXXVUUVXXWVUVWWUSQRUXXWTTVWVSTVXXWXWUTUWXXWUTTVWXVUTTUUUUTTUVVVUTRSTUTSSSUUTRRSSTTRQQPOOKHJLNQRSTSRQPQQQQQQQQQPPPPPPPPOOOOOPPPPNMNNNNNNNNNNNNMMMNOONMLLMMMMMMMMLLLLLLLLKKKKKKKKKKKJIIJJJJJJJJJJJJIIIIIIIIIIHIIIIHHHGGGGGGGFFFFFFFFFFFGGFFFEEEEEEEFEEEEEEEEEEEEDDDDDEEEEDDDDDCCCCDDDDCCBBCBBBBAAAAAABBA@???@@@@@AAA@@@@@????????>>>>>>>>>>==>>>==========================<=======<<<<<<<<;;;;;;;LLKJJJJJJKKLLKKKKKJJJJJJJJJJJKKKKKLLLLLLLLLMMMMNNNNMMMMMMMNNNNNNNNNNNNNNNNOOOONOOOOOOOOOOPPPPPPPPPPPPPQRQQQRRSTTSRRRRRRRRRRRRRRRSSSSSSSTTTTTTTTTTTTTTTTUUUUUUUUUTUUUUUUUUUUUVVVVVVVVVVVVVWWWWWWWXXXXXXXXXXXXXXXXXXXXXXXYYYYYYYYYYYYYZZZZZZZZ[[[[[[[[[[[[[[[[\[[[[[[[[[\\\\\\\\\\\\\\\\\\\\\\\\\\\\\]]]^^]]]]]]]]]^^^^______^^__`_______________``````````aaaaaaaaaaaaa`````__`abbbbba`_`cbZJ7'"'+.01,&#%,121/../.,*((+-.32'"#(-230*%#%$!'% $9Wjojfdfijihhhhhhhhhhhhhggggggghhhhhhhhhhhgggggggggggggggggghggggggggggggggggggggggggggggggggggffffffggffffffffffeeeeeeeeeeeeedddddddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaabaaaa`aaaaaaaaaa``````````````````_____^^^^^^]]]]]]]]]\\]]]]]]\\\\\\[[[[[[[[[[[[[[[[[[[[[[[ZZYYZZZZ[\^^\[ZZYYYZ[YXYYXXY[ZXVVXZ]]YSMMQUY[YVTTVWWWXYYXVVURRRQRTTSSUWXWVWWWVUSSTSSSTVVUUUVWWUTTUTTUSQQRTTUUTQNLMNOQSTTSSRQPQQQQQQQQQPPPPPPPPPPOOOPPQPNMNNNNNNNNOONNMMMNOONMMMNMMMMMMMLLLLLLLLLLKKKKKKKKKJJJJJJJJKKKKKJJIIIIIIIIIIHIIIIHHHHHGGGGGGGFFFFFFFFFGGFFFEEEEEEEFFFFFFEEEEEEEEDDDDDEEEDDDDDDCDDDDDDCCCCCCCBBBBBBBBBBAAA@@@@@@AAAB@@@@@@@@@@????????>>>>>>>>>>>>========================<=======<<<<<<<<<<;;;;;KKKJJJJJJKKKKKKKKKKKKKKJJJJKKKKKLLLLLLLLLLLMMMMNNNNMMMMMMMNNNNNNNNNNNMMNNNNONNNOOOOPPOOOOPQQQPPPPPPPPPQRQQQRRSSTTSSRRRRRRRRRRRRRSSSSSSSTTTTTTTTTTTTTTTUUUUTTUUUUUUUUUUVVVVVVVVWWWWWVVVVVWWWWWXXXXXXXXXXXXXXXXXXXXXXXXYYYYYYYYYYYYYYZZZZZYZZ[[[[[ZZZZZZZZ[[[[[[[[\\\\\\\\]]]]]\\\\\\\\\\\\\\\\\\\\\]]]]^^]]]]]]]]]]^^__````___`aa_______________``````````aaaaaaaaaaaaa``aaa```aaaa````abb\P?.$! &*,,+*% %152+,110.*&"#&-3672& !',..+(&%! !#&#.I_kkhefhkkiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhggggggggggggggggggffffffffffffffffffffffffffffffffgfffefffgffffffffffeeeeeeeeeeefeeeeeeeeeeeeeeeeddddddccccccccccccccccccbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaabbbba```a``````````````````_____^^^^^^^]]]]]]]\\]]]^^]\\\\\\\\\[[[[[[ZZZZZZZZZZZZ[\[ZZZ\\]\[YXYZZZZYXXXYYXXYZ[ZZ\[XUWZ[[YVQKMRVXXWWYZZWUUWXVUVWTPOMHGJLNOQSSQOSUUVUTUWVUSUVTTTSTUUUTUUSTWWVWVUPNMMMKMOPPQQRRRQRRRRRRRRQQQQPPPPPPPPPPPPPPPQPNNNNNNNNNNNNNNMNNNNNNNMNNMMMMMMMLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKJJIIIIIIIIIIIIIIIHHHHHGGGGGGGGFFFFFFFFFGGFFFEEEEFFFFFFFFFFFFEEEEDDDEEEEEEEDDDCCCDDEEEDDCCBCCBBBBBBBBBBAABAA@@@@AABBA@@A@@@@@@@@@??????????>>>>>>>>=======================<<<<<<<<<<<<<<<<<<<;;;;KKKKKKKKKKKKKKLKKKKKKKKKKKKKKKKKLLLLLLLLLLLMMMMNNNNMMMMMMMNNNNNNNNNMMMMNNNONNONNOPPPPPPOOPQQQQPQQQQPPPQRQQQRRSSTTTSSRRRRRRRRRRRRSSSSSSSTTTTTTTTTTTTTTUUTTUUUUUUUUUUVVVVVVVVWWWWWWWWVVVVWWWWWXXXXXXXXXXXXXXXXXXXXXXXXYYYYYYYYYYYYYYZZZZZZZZ[[[[Z[[[[[[[[[[[[[[\\\\\\\]]\]]]]]]\\\\\\\\\\\\\\\\\\\]]]]]]]^]^^^^^^^^]^__`````````aa`______________``````````a``````````aa``aaaa``aaa__bccdb]P@0$ !!#+-*'$""+33/).651,$"*1685-#"(-..+'$" %'% #5IZejkjhhhiiiiiiiiiiiiihhhhhhhhhhhhhiihhhhhhhhhhhghhhhhhhhhhggggggggggggggggggggggggggggggffffgfffffffgfffffffffffffffffffffeeeeeeeeeeeeeeeeedddddcccccccccccccccccccccccccccccbbbbbbbbaaaaaaaaaabbbbbbbcbaaa``aa```````````````____^^^^^^^^^^^^^]]\\]]]^^]]\\\\\\\\\[[[[[[[[[[[[[[[[[\[ZYYZ[\\\ZXWVWYZ[[ZYXXXXXXYZWUVWYZ\]ZVQQPLNTVXYXVVWWTRQPNKKNQSROH>;=@CEGHHGGMPQRSSVZZXSUVTTTRQRSSRQPMIGJNRUTQNLMLKLOPPQQRRQQRRSRRRRRQQQQPPPPQQQQQPPPPPPPPOOOOOOONNNMMMNNNNNNNNNNNNMMMMMMMLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKJJIIIIIIIIIIHHHHHHIHHHHHHGGGGGGFFFFFFFFGGFFFFEEFFFFFFFFFFFFFFEEEDDDEEEEEEEDDDCCCDEEEEEDCCBBBBCBBBBBBBBBBBAA@@@AABBCAAAAA@@@@@@@@@@@?????????>>>>>>>===================>==<<<<<<<=<<<<<<<<<<<<;;;JJJKKKKKKKJJKLLLKKKKKKKKKKKKKKKKLLLLLMLLLLLMMMMNNNNMMMMMMMMNMMMMNNNMMMMNNNNOONOOOPPPPPPOOPQQQQQQQQQQPPQRQQQRRSSTTSSSSSSSSSSSSSSSSSSSSSSTTTTTTTTTTTTTUUUUUUUUVVVUUUUVVVVVVVVWWWWWWWWWVVWWWWWXXXXXXXXXXXXXXXXXXXXXXXXYYYYYYYYYYYYYZZZZZZZ[[ZZZZZ[[[[[[[[[[[[[[\\\\\\\\]]]]]]]]]\\\\\\\\\\\\\\\\\\\]]]]]]]]^^^^^^^^^^]^__`_______``````````````___``````````aaaaaaaaaaaaa`aaa````aba_^bc`^XN?0$ '..("!#&+..++.5:7/& (0342+# %+/0/,&" &*(" !#"0BVfonifgiiiiiiiiiiiiihhhhhhhhhhhhiiiihhhhhhhhhhghhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggggggffffffffffffffffffffeeeeeeeeeeeeeeeeeddddccccccccccccccccccccccccccccccbbbbbbbaaaaaaaaaabbbbbbbbbabaaaaaaaaaaaaa``````````^^^^^^^^^^^^^^^]\]]]]^^]]]\\\\\\\\[[[[\[[[[[[[[[[\\[ZZZZYYXXWVVUSTVXZ[ZYXXXYXVVWTRQTY]_\WQMPRLOUXZ\WQMKLLKID@>?BFMRL@500367899;@EHHJLMPUXWRTVUUUPMNNNLJHD>:<ADGJLMNOONNNOQRSSSRRRRRRRRRRQQQQPPPPQQQQQQPPPPOOOPQPPOOONNNNMMNNNNNMMMNNNNMMMMMMMLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKJIIIIIIIIIIHHHHHHHIHHHHHGGGGGGGFFFFFFFGGFFFFFFFFFFGGGFFFFFFFFFFDDDEEEEEEEDDDCCCDEEEEDCCCBBBBBCBBBBBBBBCBAAAAAAABBBAAAAAA@@@@@@@@@@??????????>>>>>>>=====================<<<<<<<<<<<==<<<<<<<<<<IJJKKKKKKKJIJLLKKKKKKKKKJKKKKKKLLLLLMMLLLLLMMMMNNNNNNNNNNMMMMMMMMNNNNNMNNOOOONOOOOOPPPPPPQQQQQQQQQQQQQQRRRQQQRSSSSSSSSSSSSSSSSSSSSSSSSSTTTTTTTTTTTTTTTTTTTUUUUUUUUUUVVVVVVVWWWWWWWWWWWWWWWXXXXYYXXXXXXXXXXXXXXXXXXYYYYYYYYYYYYYZZZZZZZZ[[[[[[[[\\\\\\\[[[\\\\\\\\\\\\]]]]]]]]\\\\\\\\\\\\\\\\\]]]]]]]]]]^^^^^^^^^^]]^^^^^^^^^^^_````````````___``````````aaaaaaaaaaaaaaaaa``abddca^]XPG>6,$#*/,%!(.0/*'+7;93* '041+#&-0/,)&"!&)("# -AWglkkkjjjjjjjjjjjjiiiiiiihhhhhhiiiiiiihhhhhhhgggggggggggggggggggggggggggggggggggggggggggfgggggggggggggfffffffgfffffffffgffffffffffffffffeeeeddccccccccccccccccccccccccccccccbbbbbbbbaaaaaaaabbbbbbbbbbaaaaaaa`aaaaaaa``````````^^^^^^^^^^^^^^^]]]]^^^^]]]\\\\\\\\\\\\\[[\\\\\\\\\\ZZ]^_^[VQMKJJJLNPRTWXXWWXYUSRTVWXWURPPRUWUKNX[\YQGA>>AA=857899>EA7-'')*+,,-4==<;<=<>DKOORVXXULGGGFCCDBEMH?5//5=DJPWUQRTTUTSRQQQQQRRRRQQQQQQQPQQQQQQQPPPOOPPPPPPOONNNMMMMNNNNMMMMNNNMMMMMMMLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKJJIIIIIIIIIIIIIIIIIIIIIIIHGGGGGGGFFFFFFGGFFFFFFFFFFGGFFFFFFFFFFEDEEEEEEEEEEEDCCDDDDDDDCBCBCCCCCBBBBBBBBBBBAAAAAABBBAAAAAA@@@@@@@@@?????????>>>>>>>>>>====================<<<<<<<=<<<=<=<<<<<<<<<JJJJKKKKKJJJJKKKKKKKKKJKJJKKKKLLLLMMMMLLLLLMMMMNNNNNNNNNNMMMMMMMMNNNNNMNNOOOOOOOOOOPPPPPPPPPPPQQQQQQRRRRRRQQQRRRRRRSSSSSSSSSSSSSSSSSSSSTTTTTTTTTTTTTTTTTTTTUUUUVUUUUUVVVVVVWWWWWWWWWWWWWWXXXXXYYXXXXXXXXXXXXXXXXXYYYYYYYYYYYYYZZZZZZZZZZZZZZ[[[[[[[[[[\\\\\\\\\\\\\\]]]]]]]]]\\\\\\\\\\\\\\\\]]]]]]]]]]]^^^^__^^^^]]]^^^^^^^^^^_````````````___``````````aaaaaaaaaaaaa``aaaaabb`]XRG<3*$"! !!"',-(!!#)/0-((-8:5.&$-33-$ &.31,&" #$# "/@R]fpojjjjjjjjjjjjiiiiiiiiiihhhiiiiiiiiiihhhhggggggggggggggggggggggggggggggggggggggggggggggggggffgggggggffffggggggggggggffffffffffffffffeeeedcccccccccccccccccccccccccccccccbbbbbbbbbbaaaabbbbbbbbbbbbaababaaaaa``aaa`__``````_^^^^^^^^^^^^^^]]]]]^^^^^]]\\\\\\\\\\\\\[[[[[[[[[[[[[[[YVSOLKMPTVUTSQQTWZXVTUWXVTZ]ZSJB;:BNX\XLNURLF?;<@B?<:9<BFE>:95.&"! !$%&'0:;9675.*,4:<@DCDECEKMJB?@>EOJ>0'$(-08DTVRSTSTSRQQRSTSRRRRQQQQQQQQQQQQQQQQPPOOOPPPPPOOOONNNNNOOONMMMNNNMMMMMMMMLLLLLLLLLLLLLLLKKKKKKKKKKKKKJKKKJJIIIIIIIIIIHHHHHHHHIIIIIHGGGGGGGGGFFFFGGGGFFFFFFFFFFFFFFFFFEEEEDDEEEEEEEEEDDCCCCDDDDCCBBBBBBBBBBBBBABBCBBBBAAAAAAAAAAAA@@@@@@@@??????????>>>>>>>>>>>>>>=================<<<<<<==<<<=<<<<<<<<<<<KJJJJJJJJJJJKKKKKKKKKJJJJJKKKKLLLLMMMMLLLLLMMMMNNNNNNNNNNMMMMLLMMNNNNNNNNOPPPPPOOOOOPPPPPPPPPPQQQQQRRRSSRQQQQRRRRRRSSSSSSSSSSSSSSSSSSSSTTTTTTTTTTUTTTTTUUUTTTTUUUUUUUUVVVVVWWWWWWWWWWWWWXXXXXYYYXXXXXXXXXXXXXXXXYYYYYYYYYYYYYZZZZZZZZZZ[[[[Z[[[[[[[\\\\\\\\\\\]]\\\\\]]]]]]]]\\\\\\\\\\\\\\\]]]]]]]]]]]]^^^___^^^^]]^__``_______````````````___```````_``aa```aa``aaaa``abaa`_]WOF<." ! #&+-)" ',-*'&)-33/+&#'-33.(#"%+/34/)%# ##"&-9FWinjjjjjjjjjjjjjiiiiiiiiihhhijiiiiiiiiiihhhhhhhhhhhhhhgggggggggggggggggggggggggggggghggffffggggfffffgfffffgffffgfgffgfffffffffffffffffeeeddcccccccccccccccccccccccccccccccbbbbbbbbbaaabbbbbbbbbbaaabbaaaabbaaaaaaa``````````^^^^^^^^^^^__^]]]]]^^^^^^]\\\\\\\\\\\]\[[[[[[[[[[\[ZYVSPMKLOSX]_ZTPKJNTYXVTUXZYW_]QB4.)*6HW]XIGH>51/2;@?75;@DEIJD:1+'# !$&$,653130&"(+033467?KOJ>9<ACB>7,((*(!".BNRSSSSSQQQQSTSRRRRQQQRRQQQQQQQRRRRQPPOOOOOOOOOPPPOOOOOPONMMMNONMLMMMMMMLLLLLLLLLLLLLLLLKKKKKKKKKKKJJJJJJJIIIIIIIIIIHHHHHHHHHHHHIHGGGGGGGGGGFFGGGGGFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEDCCCCCCCCCCBBCCCBBBBBBBBBBBBBBBBBAAAAAAAA@@@@@@@@@@@????????>>>>>?>>>>>>>>>>>>>>>>>>>>>>>>>==<<<<<<<<<<<=<<<=<<<<<<<KKJJJIIIJJJKKKKKKKKJJJJJJKKKKKLLLLMMMMLLLLLMMMMNNNNNONNNNMMMMLLLMNNNNNNOOOOPPPPOOOOOPPPPQQPPPPQQQQRRRRSSRRQPPQRRRRQRSSSSSSSSSSSSSSSSSSSTTTTTTTTUUUUUUUTUUUUUUUTUUUUUUUVVVVVVVWWWWWWWWWWWXXXYYYYYYYYYYYYYYYYYYXXYYYYYYYYYZYYYZZZZZZZZZZ[[[[[[[[[[[[[\\\\\\\\\\\]]\\\\\\]]]]]]]\\\\\\\\\\\\\\]]]]]]]]]]^^]^^^___^^^^]^_``aa`````a`````````````___``````````aaaaa``aaa``a``aba`]XRH<0&#"#" !%)--& !#-/+$!$),+,-++,2660*'(-37641+())&!"!!%" !&%$&/@Udjkjjjjjjjjjjjjjjiiiiiihhhjjiiiiiiiiiiihhhhhhhhhhhhhhghgggggggghhhhhggggghhhhhgggggggfggfggggfgggggggfffgggggggggggffffffffffffffffffeedddcccddddddddddddddddddddddddddccbbbbbbbbbbbbbbbbbbbabaaabbaaaaaaaaaabaaaa```````a_^^^^^^______^^]]]]^^^^^]]\\\\\\\\\\\\\[[[[[[[[[[\\XSQONNPSUVVVSLD@;<BKTVWVXZZYY\R=*!##%0@QXSB95*$%)1::4*-;DE=<??8,(&# !&'#'.*'(-.$#&(,/0/-3?B=1/6B?.+*'*03-+?OQRTTSRQPPPQQQQQQQRRRRRRRQQQQRQQQPPOOOONOOOOOPPPPPPOPPPNMMMNNNMMMMMMMMLLLLLLLMLLLLLLLLLLKKKKKKKKJJJJJJJJIIIIIIIIIIHHHHHHHHHHHHHHHGGGGGGGGGFFGHGGGFFFFFFFFFFFFFFEEEEEEEEEEEEFFFEEEEECCCCCBBBBBBBCCCBCCBBBBBBBBBBBBBBBAAAAAA@@@@@@@@@@?????????>>>>>>??>>>>>>>>>>>>>>>>>>>>>>>>=<<<<<<<<<<<<<<<<<<<<<<<;KKKKKKKKKKKJJKKKKKJKKKKKLLKKKKLLLLMMMMMMMMMMMMNNNNNMMNNNNMMMMMMMNNNNNNOOOOOPPPOOOOOOOPPOOQRQQQQPPQQQQQRRRRRRRRRRRRRRRRRSSSTTSSSSQQRRSSTTTSSRRSTUUUUUUUUTUUUUUTTUUUUUUUVVVVVVWWWWWWWWWXXXXXXXXXXYXXYYYYYYYYYZYXXYYYYYYYYYYYYZZZZZZZZZZZZZZZ[[[[[[[[[[[[\\]]\\\\]]\\\\\\\\\\\\\\\\\\\]]]]^_^]]]]]]]]]^^^^^^^^^^_____^^^^^^^________``````````````````````````aaa__``_^_bbbcdb\TJ=/# "! # #*,*'$ "!!(,+'%%'(*,/0*-:B@4-+-/19>92-++-*$! !" !$"*<Remnkhgjkjjjjjiiiiiiiiiiihiihhiiiiihhhhhhhhhhhhhggggiihgggggggghhhhhgggghhiiihgggggggggggggggggggggggffggggffffffggffffffffffffeeeeeeeeeeeeeedddddddddddddcccccccccccccddddededdccccbbbbbbbbbbbabbbbbaaaaaababbba````````a_^____________^^]]^^_^]\\\\]]\\\]_`^[WX[[[\\]^_^^]\[XQG@ADFB=:3,)+237=ELSUSTX^_YG5'$# &4EMI:0+(')2;;;9/-3?FE>4-(&&&" "# $&&&$! #%&&'()(&%&'&$#$''$#"#$&)*(#!$/=FOVUSSTUSRSSRPPRUTQNNRUVTRQOMLKLLLMQRNNOOONNOQPNKKNNOPPOMLLMNONLLMMMMMMMLLKKKKKLLLLLLKKKKKKLLLLLKKJJJJJJJJJJJIIIIHHHIIIIIIIIHHGGGGGGGGGGGGGFFFFFFFGGGGGGGGFFFFFFFFFFFFFEEEEEEEEEEDCCCCCBBBBBBBCCCCCCCCBBBBBBBBBBBAAAAAAAAAA@AAAA@@??????????????????>>>>>>>>>>>>>>>>>>=>>>>=<<========<<<<<<<<<<<<<;;LKKKKKKKKKKKKKKKKJJKKKKLLLKKKLLLLLMMMMMMMMMMMNNNNNNMMNNNNNNNNNNNNOOOOOOPPOOOOPPPPPPPPPPPPQRQQQQPPQQQQQQRRRRRRRRRRRRRRRRSSSSSSSSSQRRSSTTTTTSSSTTUUUUUTUUUUUUTUUUUUUUUUUVVVVVVVWWWWWWWXXXXXXXXXXYYXXYYYYYYYYYYYXYYYYYYYYYYYYZZZZZZZZZZZZZZZZ[[\\\\\\[[[\\]]]\\\\]]]]]]]]]]]]]]]]]]]]]]]]^^_^]]]]]]^^^^^^^^^^^^^______^^^^^^^______```````````````_```aaaaaa`_aaa_^`aa__`abcaZOE>6,! ! */,&!#$%&%#&**))*+++,.147;==:50,,/4::2*&(,-(! "!*>Tckkigiiiiihijijjjiiiiiiiiiihiiiiihggghhhhhhhhhggfhiihhhhhhhhhhhhhhhhghhhiiihggggggghhhhggggggggggffffghhgffffffggfffffffffeeeeeeeeeeeeeeeeeddddddddddddddcdddddddddcdddddeeeeddccccccbbbbbbbbbbbbbaaaaaaaaabbba``a``````_____________^_______^]]]\[\]]\\\\]\[YXY\__\WQICCDHSWL>1--/.,,(#%*/138=CMTTWXUSL9( #" &1;B@3*''*/8;1./+.6AHD7)"!$%$! !%%#! #&&%# !!#%'#&4;ENQRQQQOOQRRRSSSQPRTUTPLIHDA?@ABDFKOQPOLKKLMNMLKLOOOONMNNNNNOOMMMLLMMMMLLLLKKLLLLLLLKKKKKKLLLLLKKJJJJJJJJJJJIIIIIIIHIIIIIIIHHHGGGGGGGGGGGGGFFFFFFFFFFFFGFFFFFFFFFFEFFFEEEEEEEEEDDDCCCCBBCCBCCCCCCCDCCCBBBBBBBBBBAAAAAAAAAAAAAAA@@????@@@???????????>>>>>>>>>=>>>>>>>=======<<========<=<<<<<<<=<;<;;LLKKKKKKKKKKKKKKJJJKKKLLLLKKLLLLLLMMMMMMMMMMMNNNNNNNNNNNNNNNNNNOONNNNOOOOOOOPPPPPPPPPPPPPQQQQQQQQQQQQQRRRRRRRRRRRRRRRRRSSSSSSSSSRSSTTUUUUTTTTUUTTTTTUUUVVUUUUUUUVVUUUUVVVVVVWWWWWWWXXXXXXXXYYYYYXXYYYYYYYYYYYYYYYYYYYYYYZZZZZZZZZZZZZZZZZ[[[\\\\\\[[\\]]]]\\\\\\]]]]]]]]]]]]]]]]]]]]]]^^^]]]]]]]^^^^^^^^^^^^^______^^^__________`aaaaaaaaaaa``_```aa````a`_aba_^`cca_^_`aZM?8983& ! ! (/0)"#'*(()(&&),....0358DD:4385.,1882*$!$()%""" *=Qbikihhhihfhjjjjjjiiiiiiijjiiiiiihhgghhhhhhhhhhhhhiihiiiihhhhhhhhhhhhhhhiiihgggggggggggghhhggggggggggggggggggggffffffffffeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddddddddddddddcccccccbbbbbbbbbbbbaaaaaaaaaaaaaa`aa````______________^^____^^^^^_^[[\\\[ZZ[[\\[Y[ZWOD9-&%(.?H>2&$).00001123459HSVWRF@;/&##"!+1165,'%%)087'!"$,7@B9-!%$! #$# !$%# !""#.3;DIJGEEEGKMPRRQMLNRTPIBA?=964467:=AHQRMKKKLLKJJKMNONKJKMPQOMNNONMLLLLLMMMMMLLLLLLLLLKKKKKKKLLKKKKJJJJJJJJJJJJIIIIIIIHHHHHHHHIHHHHHHHHHHHHHGGGGGGGFFFFFFFFFFFFFFFFEEEFFEEEEEEEEEDDDDCCCCBCCCCCCCCCCCCCBCCCCBBBBBBAAAAAAAAAAAAAAA@@@??@@@@@???>>>>>??>>>>>>>>>>>>>>==========<<=====>==<<<<<<<<<<<<<;;LLLKKKKKKKKKKJJJJJJKKLLMMLLLLLLLLLLLLLLMMMMMNNNNNNNNNNNNNNNNNNONNNNNOOOOOOOOPPPPPPPPPPPPPQQQQQQQQQQQQQRSSSSSSSSSSSSSSSSSSSSSSTTTTTTTUUUUUUTTTTTTTTTTUUUUUUUUUUUUVVVVVVVVVVVWWWWWWWWXXXXXYYYYYYYYXXYYYYYYYYYZZYYYYYYYYYYZZZZZZZZZZZZZZZZZZ[[\\\\\\\\\\\]]]]\\\\\\]]]]]]]]]]]]]]]]]]^^^^]]]]]]]]^^^^^^^^^^^^^_______________````_``aaaaaaaaaaa``_```aaaaaa`abcb`_`bcbba`a`[OA53991# "!!""" '00+%#%(**'%%" "',--,/5;>>A?98885248<6.)$!!#$"$##" ! "" (?Udkljhhkkhjkkjjjjjjjjjjkkkjjjjiihgghhhhhhhhhhhhiiiiiiiiiihhhhiiiihhhhhhhhhhgggggggggggghhhhggggggggggggggggfgggffffffffeeeeeeeeeeeeeeeeeeeddddddddddddddddddddddddddddddddddccccccccccbbbbbbbbbbbaaaaaaaaaaaaaaa`a`````_____________^^^^][[\\]^__^]\[\\]^_^^\\ZSH=3+&$#"""*.*%!$.8?>>;1-0573-/BQRKA;4.%"$" #!!+-')*&$# #*46)"!"'.32,#"!!#""# "# "'.8@@;3259?DILLJIHIKJE=52;?:51.-/0259@JNNOQQPOLIHHHIJJHHJNPPNLLMNMLKKKLLMMMMMMMLLLLLLLLLLKKKKKKKKKKJJJJJJJJJJJJJIIIIIIIHHHHHHHIHHHHHHHHHHHHHGGGGGGGFFFFFFFFFFGGGGFFEEEFFEEEEEEEEEDDDDDCCCCBBCCCCCCCCCCCBBBBBBBBBBBBBAAAAAABBAAA@@@@@@@@@@@@?????>>>??>>>>>>>>>==========<<<<<<<=======<<<<<<<<<<<<<<<<LLLLKKKKKKKKKJJJJJKKKLLMMMLMLLLLLLLLLLLMMMMNNNNNNNNNNNNNNNNNNOOONNNOOOOOOOOOPPPPPPPPPPPPPPPQQQQRRRRQQQRSSSSSSSSSSSSSSSSSSSSSSTTTTTTUUUUUUUUUUUTTTUUUUUUUUVVVVVVVVWWWWWWWWWWWWWWWWWWXXXYYYYYYYYYYXXYYYYYYYYYZZYYYYYYYYYZZZZZZZZZZZZZZZZZ[[[[\]]]]]]\\\]^^]]\\\\\]]]]]]]]]]]]]]]]^^^^^^^]]]]]]]]^^^^^^__^^^^__________```___```````a`````````a````aa``aaaaacdca^`bca_`bde`TE84594) "!"%&##*03+%$).+(&%# %)+-/6>EHF<8<=9/08@C>5.,($"!!!%&"!#!!""" "#! "!-CXholiilmjjkkkkkkkjjjjjkkkkkjjjiiihhiiiiihhhhiiiiiiijjjjjiiihiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggggfgfeffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeddddddddddddddddddddccccccccccccccbbbbbbbbaaaaaaaaaaaaaaaa`a``____``_________aa`_]\\\]]\[[^^]]\]^__]YVOF8-),39?B?91*&()))-5BIFGF<66;<4(%9ILA66/%"! !#%""" !!#-2-)&$####!! "&(&## (284,()/5<DKMIFDEFD<2*')++(%!!#%(+/4:@GMQSQOOPQQQMFEEEHKNOONNPOLLLLLLLMNMMLMMMMLLLLLLLLLKKKKKKKKKKKJJJJJJJJJJJJIIIIIIIIHHIIIIIHHHHHHHHHHHHHHGGGGGGFFFEEEEEFFGGGGFFEEEFFEEEEEEEEEDDDDDCCCCCCCCCCCCCCDCCBBBBBBAAABBBBBAAAABBBAA@@@@@@@@@@@@@?????????>>>>>>>>>>==========<<<<<=<========<=====<<<<<<<<<LLLLLKKKKKKKKJJJJKKKKLLMNMMMMLLLLLLLLLMMMMNNNNNNNNNNNNNNNNNNOOOOOOOOPOOOOOPPPPPPPPPPPPPPPPPQQQRRRRRRQQRSSSSSSSSSSSSSSSSSSSSSSTTUTTUUUUVVVVVUUUUUUUVVUUVVVVVVVVVWWWWWWWWWWWWWWWWWWWWXXYYYYYYYYYZZXXYYYYYYYYYYYYYYYYYYYZZZZZZZZZZZZZZZZZ[[[\\\\]]]]]]\]]^^^]\\\\\\]]]]]]]]]]]]]]^^^^^^^^^]]]]]]]^^^^^____^^__________`````````aaaa````````````aaaa`aaaaaaaabcb_]_aba_`bbc\K<3221) ##$''&"$053)%(/1)#"%$!%*.39AGJKF=;@=5,3AGC72/-*'%#"%*&!"%! "! ! ! !#-E^mmjglmjjkkkkkkkkjjjjjjjkjjjjjijiijjijiihhhiiiijijjjjjjjiiiiiiiiiiihhhhhhhhhhhhhhhhhhiiihhhhhhhhhgggggggggggffffffffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeedddddddddddddddddddcccccccccccccccccccccbbbbaaaaaabaaababa````___````___________^]]]^__]ZY\^__][[[[VPKB6(%,=KOPMIA7+$')+,08FOJIIF?89:5'.=D=40'! "$"! "'*(('&# &**($##*,)')-139AILHC>=:7.% $#! !$(,04<EKNLJJMNMJFC?=@EKKKJLOQOLMMMMNMMMMLLMNMMMLLLLLLLLLKKKKKKKKKKKJJJJJKKJJJJIIIIIIJIIHHIIIIHHHHHHHHHHHHHHHHGGGGFFFEEEEEFFGGGGFFEDDFFEEEEEEEEEDDEDDDDCCCCCCDDCCCCCCCBBBCBBBBBBBBBBBAAABBBAA@@@@@@@@@@@@???????????>>>>>>>>>========<<<<<<<<<========<<<<<<<====<=<<LLLLLLKKKKKKKJJJJKKKKLMMNMMMMMMMMMMLLLMNNNNNNNNNNONNNNNNNNOOOONOOOOOOOOOOPPPPPPPPPPPPPPPPPPQQQQRRRRRRRSTTTTTTTTTTTTTSSSSSSSSTTTTTTUTUUUUUUUUUUVUUUUUUUVVVVVVVVWWWWWWWWWWWWWWWWWWWWWXXYYYYYYYYYYYXXYYYYYYYYYYYYYYYYYZZZZZZZ[[[[[[[[[[[[[[[\\\\]]]]]]\\]^^^^]\\\\\]]]]]]]]]]]]]^^^_____^^^]^^^^^^^^^^_____________`````````aaaaaaaaaaaaaaaaaaaaaaaaabbbaabba`a`]^`bcbb_ZXQA71.*$##!"%&%#!&361++/1,"!%'%%%'*/5=DIKIFBEE@5.3>HE:-.0-*))&"!()# "%%!"""!"#! !! "!1Ogljgjkhjllkkkkkkkkkkjjjjjjjjjjjjjkkjjjiihhiiiijjjjjjjjjjiiiiiiiiiiihhhhhhhhhhhhhhhhhiiiihhhhhhhhhggggggggggfffffffffeeeeeeeffffffffffffeeeeeeeeeeeeeeeeeeeeeeeeeedddddddcccccddddccccccccccccbbbaaaaabbaaaaaaaaa``___`````______^ZZ]^^^^_``_]\\^`_\YWVUPIC9/(.@S\ULB=82+%%%&',3CMIGFJD5112'%1<?8( "#&%$&'%""%%#! $&&$" "&)(%! !!"'044/19@DB=60+'# ""! "&)/7<????@?<77;516?GHGGKNNNNNOOONNMMMMLMMMMMLLLLLLLMLLKKKKKKKKKKKJJJKKKKJJJIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHGGGGFFFEEEEEFFGGGGFFEDDFFEEEEEEEEEDDEEDDDCCCCCCDDCCCCCCCBCCCCCCCBBBBBBBBBBBBBAAA@@@@@AA??????@@@@@@@??>>>>>>>>>==========<<<<<<<========<<<<<<==>===<<<LLLLLLLLKKKKKKKKKKKLLLLMNMMMMMMMMMMMMMMNNNNNNNNNNNNNNNNNNNNNNNNOOOOOOOOOPPPPPPPPPPPPPPPPPQQQQQRRRRRRRRRSSSSSSSSSSSSTTSSSSSTTTTTTTTUUUUUUUUUUUVVUUUUVVVVVVVVVVVWWWWWWWWXXXXXWWWWWWWWXXXXXYYYYYYYYXXYYYYYYYYYYYYYYYYZZZZZYYZZZZZZZZZZZZZ[[[\\\\]]]]]]\\]^^^^]\\\]]]]]]]]]]]]]]]^^^_________^^^^^^^^^^^^^^________``````````aaaaaaaaaaaaaaaaaaaaaaaaaaaaabbba`cc``aceecYLC<740)!"!" "" ")660/03-$##$(+-/039?CDDCBBLH7+->HH=1+,//.,)# %'%!"%# "$&)(%$# ! ! $@[gkijjhjllllkkkkkkkkjjjjjjjjjkkkkkkkjjjiiiiiiijjjjjjjjjjiiiiiiiiiiiihhhiiiiiiiiiiiiiiiiiihhhhhhhhggggggggggffffgffffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddccccccccbbbbaaaabbbbaaaaaaaa`````a``````_____^_aa_]\[]^__`ba^[XVWXVQJC6*+6EOOD=:=<72-)%"$&'5ABCDGB5,(+#+9@8!&.3531+&%$#" !%&#"%&#! ! ! !#"! !" #,0.('+0330*$ !&'&%&'*,.///,'! (6BGJLOPONNOOPONMLLLMMMLLLLLLLLLLLMLLLKKKKKKKKKKJJKKKKKKJJJJIIIIIIIIIHHIIHHHHHHHHHHHHHGGGGGGGFFFFFFFFFFGGGGFFEEEFFEEEEEEEEEDDEEEDDDCCDDDDDCCCCCCCBCCCCCCCBBBBBBBBBBBBBAAAAAAAAAA?????@@@@@@@@??>>>>>>>>>==========<<<<=<<========<<<<<<======<<<LLLLLLLLLKKKKLKKKKKLLLLMMMLLLLMMMMMMMMNNNNNNNNNOOOONNNNNOOOOOONOOOOOOOPPPPPPPPPPPPPPPPPPPQQQQQQRRRRRRRSSSSSSSSSSSSSSSSSSSSTTTTTTTTTTTTUUUUUUUUUUVVVVVVVVVVVVVVWWWWWWWWXXXXXXWWWWWWWXXXXXXXXYYYYYXXYYYYYYYYYZZYYYYZZZZZZZZZ[[ZZZZZZZZZZZ[[\\\\]]]]\]\\]^^^^]]]]]]]]]]]]]]]]]]]^^^^^^_______^^^^^^^^^^^^^_______```````````aaaaaaaaaaaaaaaaaa`aabaaaaabbbbbaaefbaaa_\XK;/*,.-$!$" #+65./0.'! #&#%,02358=>=<=AGKJ>*%-?FB9441/0/,% %'% #$ "&)**(''%! ! /G\hkkjikllkkkkkkkkkkjjjjjjjjjkkkkjjkjjjjjjjjjjjjjjjjjjjiiiiiiiiiijiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhgghhhhhgggggggfffffffffffffffffffffffffffffffffffeeeeeeeeeeeeeeeeeddddddddddddddddddcccccccbbbbaabbbbabaa`aa``````aa`````_____^_a`_]]^abcc_WRLIGGIIGC=92,19>>943574.+)))*/-&-7<@CIH?/$$)6;5% $(,,+(% ! "$!!"! !%&"!#%'&$" &%! #$$$$%%$"#(2<BEGFEBCGIKMNNLLKLMNMKLKKLLLLLLLLLLLLKKKJJKKKKJJKKKKKKJJJJIIIIIIIJIIIHIHHHHHHHHHHHHHGGGGGGGFFFFFFFFFFFFFFFFEEEFFEEEEEEEEEDDEEEDDDDDDDDDDCCCCCCCBCCCCCCCCCBBBBBBBBBBBBBAAAAA@@@@@??@@@@@@?????>>>>>>>>>==>>>==========<<========<<<<<<=========LLLLLLLLLLKKKLLKKKKLLLLMMLLLLLLMMMMNNNNNNNNNNOOOOOONNNNONNNNNNOOOOOOOOPPPPPPPPPPPPPPPPPPPQQQQQQQRRRSSSSSSSSSSSSSSSSSSSSTTTTTTTSTUUUUUUUUUVVVVVVVVVVVVVVVVVVVVVVWWWWWWWXXXXXXXWWWWWWWXXXXXXXXYYYYXXYYYYYYYYYYYYYYZZZZZZZZZZZZZZZZZZZZZZZ[[[[\\\]]\\\\\]^^^^]]]]]]]]]]]]]]]]]]]^^^^^^____```______^^^^^^^______`````````````aaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbeeab`[SJD8*! $'' !"! %+11-.,&! #&((,35436<BB<8:ENP@/%(3>@=99;60/+& $'% #%$!$*--+($"!2Odlkjkkllkkkkkkkkkkjjjjjjjjjjkkjiiijjjklkkkkkjjjjjjiiiiiiihhhiijjjiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhgggggggghggggggggfffffffffffffffffffffffffffffffeeeeeeeeeeeeefeeeeddddddeeddddddddddccccccbbbaabbbbbbaa`a```__```aaa``_`_____^___^]_ab`[UNG@<9:<;60./2337850.14640-+**)+43(,5<>?EHE1!"! '11,'$$$" #&%#!!!"!" ! "" !! #(,-,,.3786436;?DILMLKKKLMLKKKKLLLLLLLLLLLLKKJJJJKKKKKKKKKKKJJJJJJIIIIIIIIIIIHHHHHHHHHHHHHGFFFFFFFFFFFFFFFFFFFFFFEEEFFEEEEEEEEEDEEEEDDDDDDDDEDCCCCCCBBBCCCCCCCCCBBBBBBBABBBBBAAA@@?@AAA@@@@@@??????>>>>>>>>>=>>>>>>>=======<<=====>===<<<<===>======MLLLLLLLLLLKKLLLLLKLLLLMMLLLLLLMMMNNNNNNNNNMNNNNNNNNNNNNNNNNNNOOOOOOOOPPPPPPPQQQQQQQQQQQQQQQQQQQRRRSSTSSSSSSSSSSSSSSSSSTTUUUTTSTUVUUUUUUUVVVVVVVVVVVVVVVWWVVVVVVWWWWWWXXXXXXXWWWWWWWWXXXXXXXXXYYXXYYYYYYYYYZYYYZZZZZZZZZZ[[[[[[[[[[[[[[[Z[[\\\\\\\\\[\]^^^]]]]^^^^^^^^^^^^^^^^^^^^^____`````____^^^]]]^______`````````````aaaa`aaaaaaaaaaaaaaabaaaabbbbbbbbba_`]TE70' "#'*)*,-(!%'(08=:515?GG?;>KPI2&)4=>>=;9873+#"%%!"%# #'+($!!" #%&%" !"! %$!A[iiilllkkkkkkkkkkkkkkjjiiiiijjjihhijkkllllllkjjjjiiiiiijihhiijjjjjiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhggfffghhgggffggggggffffffffffffffffffffffffffffffffffffffffffffeedddddeeeeddddddddddddcccbbbabbbbbbbba`a``````aaaaa````_____`a_^^^__\SF96=<:;<;3) !(266971.1:<:44994-$!-0)/9@?;;>?- &)*$$)'$$" "%#$+-(" "##" ""!!! " !"!$)01*#!$&%&),05<CHKLLKKKLJIJKKLLLMMLLLLLLKKJJJJKKKKKKKKKKKKJJJJJJJIIIIIIIIIIHHHHHHHHHHHIIGFFFFFFGGGGGGGGFFFFFFFFFFFFFEEEEEEEEEDEFEEEEDDDDDDEDCCCCCCCBBBBBBBBBBCBBBBBBBBBBBBBBBAA@@@ABBA@@@@??>>>??>>>>>>>>>=>>>>>>>=======<<========<<<<<<=========KKKKKKKKKKKKKKKKKKLLLMMMMMMMMMMMMMMMMMMMMMMLLLMMMMMMNNNNNNNNNNOOOOOOOOOOOOOOOPPQQQQRRQQQQQQRRRRSSSSSSSSSSSSSSSTSSSSTTTTTTTTTTTTTUUUUUUUUUUVVVVVVVVVWWWWWWWWWWWWWWWWWWWWWWWWWWWWXXXXXXXWWWWXXXYYYYYYYYYYZYZZ[[YYYYYZZZZZZZ[[[[[[[[[[[Z[[[[[[\]\\[\[[\]\\]]\\\\]^^]]]\]]]]]\]]]]^^^__^^^__`_]]^^^^^^^^]^_``_______``````````aa```aaaa`````aaaabcccbbabbbbbbccbab_TA4+$!" "&*+)$',0)%/.*4?D8/5?D=528DPH6-*/9ACB@=;94-&!"'(%#"#!!&+)&%&(&#! ,DZfjijkmmkiiklkiiijjjjjjjkkkkkkjijjjiijjjjkkkjjjjjjjkkkkkjjiiiiiiiihhhhhhhhiiiiiiiiiiiiiihhhhhhhhhhggggghhhhgffeeeeffggfffgffffeeefffeeeeddddeefffffffffeeeeeeefffeeeeeeeeeffeeddddddddedbcccccaabbbbbaaaaaa`````aba_ab`ab``b_^a`^^_ca\VC12;GQWVH4($()+.,+8<4,,6;;868;6%!&-.*)/;@?=942/+(& "+.,'#"# ',+% !#" #%%$#" "(-,-1:BFHHIIHEDHKLLMNMLKMNKIJJKLLLLLLLLLLLKKJJJJJJJJJJIIIIIIIJIIIIIHHHHHHHIHHHGGGGGGGGGGHGGGFFFFFFFFFFEEEFFFFFFFEEDDDDEEDCCDDDDCCCCCCDDDDDCCCBBBBBCBBBBBBBAAAAAAABBAAAA@@@@@@@@???>>?????????????>>>>>=================<<<<<<<<========KKKKKKKKKKKKKKKKKKLLLMMMMMMMMMMMMMMMMMMMMMMLLLLMMMMNNMNNNNNNNNNOOOOOOOOOOOOOOPPPQQQRRQQQQQQRRRSSSSSSSSSSSSSSSTTTTTTTUUUUTTTTUUUUTUUUUUUUUUUVVUUVVVVVWWWWWWWWWWWWWWWWWWWXWWWWWWXXXYXXXXXWWXXXXYYYYYYYYYYYZZ[[[YXYYYZZZ[[[[[[[[[[[[[[[[[[[\[[\\\\[[[[\]\\\\\\\]]]^^]\\]]^]\\\]^]^____^^^__`_^]^^^^^^^^^__``_______```````aaaaaaaaaaaa`````aabbbcccbbbaaabbbccace_O7)&"!"$# ! !!!%*,-(%%*/-&!(.//8>?74?FF7.0=IK<*-39>@@@A?;61*$"" #(*(%"!&*+*'&&&" 0H]iiijlmljiklkiijjjjjjjkkkkkkkjjjjjiiijjjjkkjjjjjjkkkklkjjjjiiiiiihiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhggggghhihgffeeeeffgggggffffeeeefffeeeedddddefffffffffeeeeeeefffeeeeeeeefffeeddddddddedbcccccbaabbbbaaaaaa`aaa`acc`cd``a__b_]`aabca[RK<3@KPSRM?.&(264,)+791+*179:::=9+)-+))(-8?@>7010'#"!+-,'" "" ),*#!"! "$$#! #+-,,2;DIJJIGEDIKLMNOMKKLNJIJJJKKLLLKKLLLLKKJJJJJJJJJJIIIIIIIIIIIIIHHHHHHHIHHHHHHHHHHHHHHHGFFFFFFFFFFEEEFFFFGFFFFEDDCDEEDCCCDDDBBCCCCDDDDDCCCBBBBBBCCCBBBBAAAAAAABBBBBB@@@@@@@@??????????????????>>>>>===<<<<======<===================KKKKKKKKKKKKKKKKKLLLLMMMMMMMMMMMMMMMMMMNNMMMMMMMNNNNNNNOOOOOOOOOOPOOOOOOOOOOPPPPQQQRRQQQQQQRRRRSSSSSSSSSSSSSSTTTTTTTUUUUUUUUUUUTTUUUUUUUUUUVVUUVVVVVVWWWWWWWWWWWWWWWWWXXXXXXXXXXYYYXXXXXXXXXXYYYYYYYYYYYYZZZZYYYZZZZ[[Z[[[\\[[[[[[[[[[\\[[[[[[[[\\\\\\\\\\\]]]]]^]\\]^^^]\]^^^^_`_______``___________````_______```aaaaaabbbbbbbbbbaaaababbbccccbbbbbbbbbbbbdf^L2%#$&&$"!#"!#(/0-%"*01'!"(,,05::77;DGB508EJ@2*4>CB><=@?92/+$#%%$#'**% !)+*'()'# # "# !5Pcijijkklkkkjjjkjjjjjkkkkkkkkjjjjjjjjjjjjjkjjjjjkkkkkkkjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhggggghhhhhgffffffggfggffffeeeeeeffeeeeeeeeeefffffffffeeeeeeefffffeeeeeeeffeeedddddddedbcccccbbbbbbbbbbbaaaaaaaaccacc``a^^b`_`acfbYQH@<?MRME<62/,/8?=.,02/*)+0469:898798-&&)/5;?>81.*! ")'$$! !"!!#)+(" *36448@GJJIIIJKLMMNMMLLMMJIJJJIIJKLLLKLLLKKJJJJJJJJJJIIIIIIIIIHIIIHIIIIIIHHHHHHHHHHHHHHHHGFFFFFFFFFFEEEEFFFGFFFFEEDCDDDDCBCDDDCCDDDDDDDDCCCBCCCCBBBBBBBBBAAAAAAAAAAABA@@@@@@@@??????????????????>>>>>====<<<======<<==================KKKKKKKKKKKKKKKKLLLLLMMMMMMMMMMMMMMMMMMNNNNMMMMNNNNNONNNNNOOOOOOOOOOOOOOOOOOPPPPQQQRRQQQQQQRRRRSSSSSSSSSSSSSSTTTTTTTUTTTTTTTTTTTTUUUUUUUUUUVVUUUUVVVVWWWWWWWWWWWWWWWWWXXXXYYYYYYYYYXXXXXXXXXXYYYZYYYYYYYYYZZZZZZZZ[[[[Z[[\\\\\\\\\\\[[\\\[\\\\[[\\\\\\]]]]]]]]]]^^]]]^^^]]]^^____``____```````````aaaa`````____````abaaaabbbbbbbbbbbbbbbbcccccccbbbbbbbbbbadfeZF-""$&&# #$" !$)/0+&%.0+!&/557:<;98:??;34>FD627@FGC?>?@;5/-)#"%(('()'$!!&.,(##%# ! #$!%B[gkjjjkmmkjijklkkkkkkkkkkkkkjjjjjjjjjjjjjjkkkkkkllllkkjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiihhhhhhhhhggggghhhhggfffffgggggfffffeeeeeeeeeeeeeefffffffeeeefffffffffgfffeeeeeeeeffeeedddddddedcccddcbbbbbcccbbbbaaaaaaa`^]_a`abaacdc```aZNID99?EE?83269536>?4/.($"'.24552-,1>HI7)%',.7>?:30+"! #'!#"!!! "$'(% "! " )9B@;78<AFIJMNMMMMMLLMNONIHJKKJJKMNNNLKKKKKKKKJJJJJJJIIIIIIIIIHIIIHHHHHHHHHHHHHHHHHHHHHHHGFFFFFFFFFFEEEEFFFGFFFFEEDCDDDCBBCCDCBCDDDDDDDCCCBBCCCBBCBBBBBBBAAAAABAAAAAAA@@@@@@@@@@????????????????>>>>>=====<<======<<<<<<<<<<<<<<<<<<<<KKKKKKKKKKKKKLLLLLLLMMMMMMMMMMMMMMMMMMNNNNNMMMMNNNNNNNOOOOOOOOOOOOOOOOOOOOOOPPPPQQQRRQQQQQQRRRSSSSSSSSSSSSSSSTTTTTTTUUUUUUUUUUUTTUUUUUUUUUUVVUUUUVVVVVWWWWWWWWWWWWWWWWXXXXXYYYYYYYYYYYXXXXXXYYYYZZYYYYYYYYYZZZYZZZ[[[[[[\\[[[[[[[[[[[[\\\\[[[[\\\\]\\]]]^^^^^]]]^]\\]^^^]\]^^^__```____```````````aaaa`````____````abaabbbbbbbbbccccccccccccccccbbbbbbbbbbcfe_Q>*!"""!""!##&+/.(),2,#"&-6=@=8:<=:8;856=EB:4<GFA=>AFE>500-)%$&*./,($#&*,*&#! !#""!!" !"!##!!"$%%$#"! 4N`jlkjknnljiklkkkkkkkkkkkkkkjjjjkkkkjjjjijkkkkllllllkjjjjjjjkkkkkkkjjjjjjjiiiiiiiiiiiiiiihhhhhhhhggghhhhhhggfffggggggfeffeeeeeeeeeeeeeefffffffeeeeeffffffffggffffeeeeeeeefeeeeeeeeddddccddddcccccdddccbbbbabbcba_^`bcdca__^]\ZXYTKLH88ABBA=846:73012--.&!!&-034786..>MR@0()+),231+*'!!#""$%$"! !##"!!%" #""4AE@823;EKJMNLKKLLKKLLLKIJLMMLKLMMMKJKKKKKKKKJJJJJJJIIIIIIIIIIIIIHHHHHHHIHHHHHHHHHHHHHHHGFFFFFFFFFFEEEEFFFGFFFFEEDCDDDCBBCCCCBCDDDDDCCCCCBBCDCCBBBBBBBBBBBBBBBAAAAAAA@@@@@@@@@@????????????????>>>>>>============<<<<<<<<<<<<<<<<<<<<KKKKKKKKKKKKLLLLLLLMMMMMMMMMMMMMMMNNNNNOONNNNNNNNOOOOOOOOOOOOPPPPPPPPPPPPPPPPPPPQQQRRQQQQQQRRRRSSSSSSSSSSSSSSTTTTTTTTTTTTTTTTTTTTUUUUUUUUUUVVUUUVVVVVWWWWWWWWWWWWWWWWWXXXXXXYYYYYYYYYYYXXXXYYYYZZZZYYYYYYYYYZZZZZ[ZZZZ[[[[[\[[[[[[[[[[\\\\\[[\\\\]]]]]]]^^^^^]]]^]\\]^^^\\\^^]^__`__^^_```````````aaaaa```````````aaaaaabbbbbbbbccccccccccccccccccccccccbcdhcVE4'!" !$$)01,&+13*!)28>AA:38=>==>:6=EG=6>KSG<9@ILF<301,(()*.24/)$&+.+#!!" ! #&&#$$ #%" ""! !$#! """! !#""!!!! '?Wfnlkkmnljjlkjkkkkkkkkkkkkkjjjkkkkkjjjiijjkkkllllllkjjjjjjjkkkkkkkkjjjjjjjijiiijjiiiiiiiiiiihhhhhhhhhhiihhhgggghhhggfffeeeeeeeeeeeeeefffffffeeeeefffffffffgffffffffffeeeeeeeeeeeeedddcdddddccccdddcccbbbaaabbb`_^_aceda^[YY[ZWWSKLE/1?B?:8511364.)')+,(&'),-.16=A2)6FPE6))*''(('$%&##$! !'& #!!"#&!,9?>947?IMJLNLLLMMMLLKHFHJLMMLLLLLLIHJKKKKKKKKJJIJJJIIIIIIIIIIIIIHHIIIIIIHHHHHHHHHHHHGGGGFFFFFFFFFFEEEEFFFGFFFFEDDCDDDCBBCDDCCCCCCCCCCCCCCCDDDCCBBBBBBBBBBBBBBAAAAAAA@@@@@@@@@?????????????????>>>>>>>>==========<<<<<<<<<<<<<<<<<<<<LLLLLLLLLLLLLLLLLLMMMMMMMMMMMMMMNNNNNNNOONNNNNNNNNNOOOOOOOOOPPPPPPPPPPPPPPPPPPPPQQQRRQQQQQQRRRRSSSSSSSSSSSSSSTTTTTTTUUUUUUUUUUUUTUUUUUUUUUUVVVUVVVVVWWWWWWWWWWWWWWWWWWXXXXXXXXXYYYYYYYYYYYYYYZZZZZZYYYYYYYZZZ[[[[[[[[[[Z[[[[[[[[[[[[[[\\\\\\\\\\]]]]]]^^^^^^^^^^^]\\]^^^\\\^^]]^___^^^^_```````__``a```aa````````aaaaaaaaaabbbbbbbbbbbbbbbbbbbbbccccccccccdg^J8*$! "!!"$%-53,%,31,)3>FD=5//:?=BGEA>FID::NWTD<ALRLC:642*&,02454/+)+-+&!# !!!"#$&'% "!&&# %&$ #%$" ! #&&%%$$#"!! 0Kannmkmmmkklkikllkkkkkkkkjjkkkkkkkkjjjiijkkkkllllllkjjjjjjjkkkkkkkkkjjjjjjjjjijjjjiiiiiiiiiiihhhhhhhhhiihhhhhhhhhhggggfffeeeeeeefffffggggfffeeeeffffffffggffffffffffffeeeeeeeeeeeeedddddeedcccccdcbbbaa```_```_]\\^bdc`\XWX]^]ZQFB:&*;@9+)+*+-462*%**(),--,+*,08@3',:HE8&$&''&$""$''&$ (&!" "!#% (/247:?EKKFILLLMOONNLJGFHJKLLKKKLLKJJJJKKKKKKKKJIJJJIIIIIIIIIIIIIIIIIIIIIHHHIIHHHHHHHGGGGGFFFFFFFFFEEEEFFFGFFFFEDDCDDDDBBCDDCCCCCCCCCCCCDDDDDCCCCBBBBBBCBABBBBAAAAAAA@@@AAA@@@@????????????????>>>>>>>>==========<<==================LLLLLLLLLLLLLMMMMMMMMMMMMMMMMMMNNNNNNNNOONNNNNNNNONNOOPOOOOOOOPPPPPPPPPPPPPPPPPPQQQRRQQQQQQRRRRSSSSSSSSSSSSSSTTTTTTTUTTTTTTTTTTTTUUUUUUUUUUVVVVVVVVWWWWWXXXXXXXXXXXXXXXXXXXXXXXXYYYYYYYYYYYYZZZZZZZYYYYYYZZZZ[[[[[[[[[[[Z[[[[[[[[[[[[[\\\\\\\]]]]]^]]^^^^^^^^^^^^]\\]^^^\\\^^]]^___^^^^___``````````aaaaa````````aaaaaaaaaabbbbbbbbbbccbbbbbbbbbccccccccccbcV?-" !" !$&(,250)%-2,/5;ENF6%&3?DBHKDBDJH>;DZWF>BOWUE;9<9/&$-4873.+,..+&"!!!!!$&%####" #'''&%$$$%&&&$! ##! !! !!"$&''&&%$"! "#""! '>Winnkllmllmkiklllkkkkkkjjjkkkkkkkkjjjjjjjkkkklllllkjjjjjjjkkkkkkjjjjjjjjjjjjjjjjjjiiiiiiiiiihhhhhhhhhiiiihhhhhhhhggffgfffeeefffffgffggggfffffefffffffffggfffggffgffffeeeefffffeeeedddeeeddccbbcca````___^^^^^aa^\\``^[WSTWZ^^TC201,4=7,"#(/11451)$)%#(++,.-+()..,*3?A5 #&'&%$&()'# &$!" "#! ## (,+'',49;<;;BHHIIHJLLKJKLLLLLLLLLLLMMMKJJKKKKKKKJJJJJIIIIIIIIIIIIIHIIIIIIIHHHIIIHHHHGGGGGGGFFFFFFFFFEEEEFFFGFFFFEDDCDEEDCCCDDDCBBBBBBCCCCDDDDCCCCCBBBBBCBBABBBBAAAAAAA@@AAAAAA@@@???????????????>>>>>>>>>>>>>=====<<<<<<<<<<<<<<<<<<<<LLLLLLLLLLLLLMMMMMMMMMMMMMMMMMMNNNNNOOONNNNNNNNNNNNNNOOOOOOOOOPPPPPPPPPPPPPPPPPPQQQRRQQQQQQRRRSSSSSSSSSSSSSSSTTTTTTTUUUUUUUUUUUTTUUUUUUUUUUVVVVVWWWWWXXXXXXXXXXXXXXXXXXXXXXXXXXXYYYZZYYYYYYYZZZZZZZZZZZZZZ[[[ZZZZZZZZZZZZ[[[[[[[[[[[[[[\\\\]]]]^]^^^^^^^^^^^^^^^_^]]^^_^]]]^^^^_____^^^___``````````aaabaaaaaa`aaabbbbbbbbbcccccccccccccbbbaabbbccccddcccca]L4&!"#!""" &)-242*').0*2?ADH>0#*;BGHJH?@FIE>CNZL8;GSTM>8:>9-'(06750++-0-'##%$#!!$(**)'$" $),.+%$$%&&&&('&" !!#$###"""#$%'''(())'%#""!"$&%%$#""#$#"! "" "1Jalolkkllmnlikllllkkkkkjjjkkkkkkkjjjjjjjjjjjjkkkkkkkjjjjjjjjjjkkjiijjjjjjjjjjjjjjjjiiiiiiiiiihhhhhhhiiiiiihhiiiihgggggffffeeffffggggggffffffffffffffffffffffgggggffffeeeefffffeeeeddeeeeedcbbbbca`````___^^]]^^\ZY]\YVSQUZ\`^J4%'08><+$+-/243431+(+' !"$%',.-'$&()%'-3.!!&**%"###$"#"""#" !! #*+& ')'&(+4<>?>=?CEDDFJMNMLLMMLKKKLLKKKKKKKKKKKKKJJIIIIIIIJIIIIIHHHHHHHIHHHIHHHHGHGGGGGGGFFFFFFFFFEEEEFFFGFFFFEDDDDEEDCCDEDDCCCCCCCBCCCDDDCCCCCCCCCBBCBBBBBBBAAAAAA@@@AAAAAA@@@???????????????>>>>>>>>>>>>>=====<<<<<<<<<<<<<<<<<<<<LLLLLLLLLLLLLMMMMMMMMMMMMMMMMMMNNNONNNNNNNNNNNNNMNNNNNNOOOOOOOOPPQPPPPPPPPPPPPPPQQQRRQQQQQQRRRRSSSSSSSSSSSSSSTTTTTTTUTTTTTUUUUUTTUUUVVUUUUUVVVVWWWWWXXXXXXXXXXXXXXXXXXXXXXYYYYYYYYYZZZYYYYYYZ[[ZZZZZZZZZZ[\\[ZYZZZZZZZZZYZ\\\\\\\\\\\\[[\\]]]]^^^^^^^^^^^^^_______^^____^^^__^^______^__````````aaaaaaabbaaaaa`aaaabcccccccccccccccccccccbbbbbbbcccdddcccebWB+! #$###!"&).43.()0-**6DE@8/+-8CACIFB?CHCBDQWOA9EPRKB==>;3*,1552101000,'%%'&%$'+./-,)" '-00/0-'$#$%&$&)+)# "&'&%%$%%')*,+)*,--+(&%%%&'(((''''()'%#! ! !"'>Xionkjlmmomjlmlllllkkkjjjlllkkkjjjjjjjkjjjjjjkkkkllkjjjjjjjjjjjiiiijjjjjjjjjjjjjjjjiiiiiiiiiihhhhhiiiiiiihiiiiihgggggggffffffffghhgfgffefffggggggffffffffefggffgffffeeeefffffeeeeddeeeeedbbaaabaaaaabba`___^]\]ZY\ZTPMNU\\]V<(#(4?A:++7:9767751-++*'$!!"#'*+&$%$#$"!)+%##&(($"#!"! !!! ! #$# !'-4778<A@><>BHJHHILMMKJIJKKKKKJJJJJKKKKJJIIIIIIIIIHIIIHIIIIIIHHHHHIIIHHHGGGGGGGFFFFFFFFFEEEEFFFGFFFFEDDDEFFECCDEEDDCDDDDCBBCCCDDCBCCCCCCCCCCCCBBBBBAAAA@@@@@AAAAAA@@@@??????????????>>>>>>>>>>>>>=====<<<<<<<<============LLLLLLLLLLLLLMMMMMMMMMMMMMMMMMMNNNOOOOONNNNNNNNNNNOOOOOOOOPOOOOPQQQQQQQQQQQQQPPQQQQRRQQQQQQRRRRSSSSSSSSSSSSSSTTTTTTTUUUUUUUUUUUUTUUUUUUUUUUVVVWWWWWXXXXXYYYYYYYYYYYYYYYYYYYYYZZZZZZZZZZZYYZZZ[[ZZZZZZZZ[[[[\\ZZZZZZZZZZZZ[\\\\\\\\\\\\\[\]]]]^^^^^^__^^^^^^____``____```___`_^^_```____`aaaaaaaaaaaabbbbbaaaaaaaabbccccccccccdddcccdddddccbbbbbccddddddccfdS;& #$%%#! !$%%&+31+)-5*$.;EG<("*>FE<;B@@GKJ?@O^[@>LY[PD=CEB5+(1;:4-.4;93.,*(&&&(+/220.-)" !"'09><70-*)&##$$#%*,) "$')(((((((*+-/0/-/111/-+*)*,)()*++,,-,*'$#! "#"6Peonkjlmmonllmmlllkkkkjjjlllkkkjjjjjjjkjjjjjjjkkklkkkkjjjjjjiiiiiiiiiijjjjjjjjjjjjjiiiiiiiiiihhhhhiiiijjiiiiiiihgggghgggfffggggghhgggfffffggggggggfffffffffgggggffffeeeefffffeeeeddeeeeedcbbbbbbccccddcbaa````a^\^YPKHGOVTPE.#(/6<<8:;;>@<;==80-+),/*&&%#""#!!$!")(%*,(""%'&#" ## !"" !!! #!%%"!! $.58;?C@;558<==?CILMKJIKLKKKJJJJJJKKKKJJIIIIIIIIIIIIIHIIIIIIIHHHIIIHHHHGGGGGGGFFFFFFFFFEEEEFFFGFFFFEDDDEFFEDDDEEEDDDDDCCBCBCCCCCBBCCCCCCCCBCCCCBBBAAAA@@@AAAAAAAA@@@@@?????????????>>>>>>>>>>>>>=====<===================KKKKKKKKKKKKLMMMMMMMMMMMMMMNMMMMNNNNOOONNNNNNNNNNNOOOONOPPPOOOPPPPPPPPPPPPPPPPPPQQRRRRQQQQPQQRRSSSSSSSSSSSSSTTTTTUUUUUUUUUTTTTTUUUUUUUUVUVVVVVWWWWWWWWWWWXXXXXXXYYYYYYYYYYYYYZZZZZZZZZYYYYZZZZ[ZZZZZZZZ[Z[[[ZZYZZZZZZZZZZ[\\\\[\\\\\\\]]\\\\]^^^^^]__^^^^^^^^__``__^^__````__^_``a``__`aaa````````aaaaaaaaaaaaaaaaabbbbbbccccccccccddddddcccccddbceedcddcd_M8)!!%(($!"%" #)/3/*+.1)(:DE;-#1DNF76:AEJPI@CN]aV@EVWOC?@EA7/,16965689972,'$$'+/24541-+**(')1;EFC;50-*)-0+('),-+& ! ! !%&&%%'*-0/++-/000000001221100000000-+-../010.*('&# ! !"*>[kokjlmmonllllllllllkkjkllllklkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjiiiiiijjjiiiiiiiiiiiiijjiiiiihhhhgghhggggggggggggggffffffggfffffffffffffffggggggggffffffffffeeeeeeeeeeeeddcccccdeecbbceda``adb^_^ZVUZTD??@90-+)+/357;=<98<=<962,))))())# !" !'))(((% $%%$%&'%"""! ! !!"$# !!""#%# !!!!!#!! ",11.-/.,*)+//,4@ILIIHHFGMKFEGKLKJJJLLJKKJIIHIJJIGHIIIIIIIHIIHHIIIHIHHHHHHHHGGGFFFFGFFFFEEEEEEEFFFFFFEDEEEEEEEDEDCDDDCCCCCCBBBCCBCCCCCCCCCBBBBBBAAAAAAAA@@@@@AABAA@@?????????????>>>????????>>>>>>>==<======<<<<<<<<<<<<<KKKKKKKKKKKKLMMMMMMMMMMMMMNNMMMMNMNNOOOOOOOOOOOOONOOOONOPQPOOOPPPPPPPPPPPPPPQPPPQQRRRRQQQQPQRRRSTTTTTTSSTTTTTUUUTUVUUUUUUUUUUUUUUVUUUUUUUVVVVWWWWWWWWWWWWXXXXXXXXYYYYYYYYYYYYZZZZZZZZZYYYYZZZZ[ZZZ[[[[[ZZZZ[[ZZZZ[[[[[[[[[[[[[[\\\\\\\]]\\\\]^__^]]^_^^^^^^^^__``__^^__`a``_^^_```a````abaaaaaaaaaaaaaaaaabbbbaabbbbccccbccccccccccdddddddcccdddbcfecbdec_WG6+"!&)*'#!"!'.43/**-1/3BD=0).BQOA48@FORMFDOZa^SEIRLD>BFF=3003:?<<<;951,(%%+16:<:61-**-004:BGGD?:51..255/+*+)'&$! !"" ! "&*-...0355542211112344554222234455554335433342/+*)(&#""##!!"#0Mbjkklllmmmmmmmmmmmllkkkkllllllkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjiiiiiiijjjjijjjjjjjjjijjjiiiiihhhhhhggggggggggggggggggggggggggggggffffffffgggggggggffffffffffeeeeeeeeeeeeeeddddddeedccdfdb`acdb^_^WTTXN8.+-*)23,*+-0368943<?<62/+*.,'&&%!"#$"!#')'&''$ $*,-+)('&$"!!""!#$$###!!#!!"! "&&$ !$#"&'$ $)*% "! "! !()'!!#$$%&&$#.>JMIEBCFJMICBEJKLKJJKJIJKKJIIIJJIHHIIIIIIIHIIIIIIIHHHHIIIHHGHGGFFFFGGFFEEEEEEEEFFFFFFFDDEEEEEEDDDCDDCCBCCDCBABDCCCCCCCCCBCBBCBBABBBBBBBAA@@@@ABBAA@@??????????????>>????????>>>>>>>==<=====<<<<<<<<<<<<<<LLLLLLLLLLLLLMMMMMMMMMMMMMNNMMMMMNNNOOONNOOOOOOOOOOOOONOPPPPOOPPPPPPPPPPPPPQQPPPQQRRRRQQQQQRRRRSTTTTTTTTTTTTTTUUUUUUTTTUUVVVVVVVVVVVUVVVVVVVWWWWWWWWWWWWXXXXXXXXYYYYYYYYYYYYYZZZZZZZZZZZYYZZZZ[[[[[[[[[ZZZ[[[[Z[[[[[[[[[\[[[[\\\\\\\\\]]\\\\]^__^^]^_^^^^^^____``__^^__```_____`_```aaaaaaaaaaaaaaaaaaaaaaaaaaabbbbccccccccccdddcccdddddddcccddccegdbadd_WMA4)"#&)*)&" &.463.++.4:>A;2,1?ILC<<AHMUSEEQ]c`VLHFEA@CIKD<6875=EFC<731+'(*.8?@A?93-*)-499AJLG?;::8514;940/.*# %''%$""$%" "! !"""&/345668:<=;98974101258:;<833579:;;;:99:<=;865541.,+))))'%"! " (@Vekmmlkkmnnnnmmmmmlllkkllllllllkkkkklllllllllllllkkkkkkkkkkkkkkkkkkkkkkkkkjjjjiiiiiijjjjjjjjjjjjjjjjjjjiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhgggggggggggfgggggggfffffffffffeeeeeeeeeeeeeeddddddeddeedcbbcdcbb_XPJFD9)%%%'-77-))+.145523<?<4-*(*1/($ !$$%'%" #&&%$&'')-353-&!!"%("%)*('&'$"" "&&# #%#!'*'!&/3-!$# !! #! #$&' "-:DIF@;:DKIFB@BFHJKLKJIHIJKKJJIIIIJJJJIIIIIHHHHHHHIHGGHHHHHGGGHGFFFFGGGFEEEEEFFFFFFFFFEDEEEEEEEDDDDDDCBBCCCCBBCDDCCCCCCCCBBBBCBBBBBBBBBBAA@@@AAAAAAA@??@@???????????>???????>>>>>>>>==<<<<<<<<<<<<<<<<<<<<LLLLLLLLLLLLLMMMMMMMMMMMMMNNMMMMMNNNONNNNNNNNNNOOOOOONNOOPPPOOPPPPPPPPPPPPQQQPQQQQRRRRRRRRRRRSSSTTTTSSSTTTTTTTUUUVUUTUTUUVUUUUUUVVVVUUVVVWWWWWWWWWWWWWXXXXXXXXXYYYYYYYYYYYYYZZZZZZZZZZZZZZZZZZ[[[[[[[[[ZZ[[[[[[[Z[[[[[[[[[[[[\\\\\\\\\]]\\\]]^^^^^^__^^^^^^____``______`````````````aabaaaaaaaaaaaaaaaaaaaaaaabbbbbcccccddddddddcccddddddddcccbbdffbaceaWK@90& #')*)(&$!$,464/,-/49AB91.3=GD<69CJOOPMEJXglaL>CB>?DKOL@::BECBDHC92/.*).5=ACA<61-+,05<ABGJHB=;:;:899863343+##(+($#" !#(*)% "%((%! !!!"%).37;=@A@???@?><;:8778:<=>>>;9:<>@ABA@?>=?@?=9876541//--//,'#""! ""6Ndmpmllmnnnmmmmmmmlllkllmmllllllllllmmmmmmmmmmmmlkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhggggggggggggggggfffffffffffffffeeeeeeeeeeeeedddeedccccbbbcefcdf_QB5.+'!',(*074($%*.399546==80)'&(--("!""$')'""&&$!!$&%&,23-%#!$)($ #(+,)&%'#"$""%%# #&% $/51'"($ ! ""#$%+15;>:517?CB?=>ADHLNMJIHIJKLLKJIIJKKJJJIIIIHHHHHHHHGGGHHHHGGGHHGGGFFGGGFEEFFFFFFFFFFFFFEEEEEEEEDDDDDDCBBCCCCBBCDDCCCCCBCBBBBBBBBBBBBBBBBA@@@AAAAAAA@@??@@@??????????????????>>>=>>====<<<<<<<<<<<<<<<<<<<<LLLLLLLLLLLLLMMMMMMMMMMMMMNNMMMMNNNNOONNMNNNOOOOOOOPONOOOOPOOOPPPPPPPPPPQQQQQQQQRRRRRRRRRRSRRSSSTTSSSSSTTTTTTUTUUVUUTUTUUUUVVVVVVVVVVVVVVWWWWWWWWWWWWXXXXXXXXXYYYYYYYYYYYYYZZZZZZZZZZZZZZZZYZZ[ZZZZZZZZ[[[[[[[[[[[[[[[\\\\\\\\\\\]]]]]]]]]]]]^^^^^^^^^^________``_____`````````````aaabaaaaaaaaaaaaaaaaaaaabbbbbbbbccccccddcddddccddddddddddcbaadfeaaee\J=51)!!$*..+'$"""!")1773,+/6=AHE5+,6=<4/5>HMQTPJGN[kkYG?IF?GORNG>=AHKJECG@50./,-5=CB>:3-**-29?DHJIFDCB@><<?A?98751-(&*..)$!""!"$'('$"!(..-*&! !! !#$'*/6=@>@CEFECAAABB?::<>@BCCBBA?@ABDEFFFEEDCCBA?=:988875320022.)%#$#"!/IbmplnoonnnnnnmmlllllmmmmlllllllllllmmmmmmmmmmmmlkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjkkjjjjjjiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhgggggghhggfggggfffffffffffffffffeeeeeeeeeeeeeddfgcbcccbcdddcadibTE7-'" $)*,.1.&&(**+/2136850+**((*($ $''!!#$# !#$# #)0,%##!!%'%"#'+,+&" ! !!!"%&%!$040& #%$! "" #%%&+242.27874335:@GKKIHIIIJKLKJIIJKKJJJIIIIHHHGHHHHGGGHGGGGGGGGGGGFFGGGFFFFFFFFFFFFFFFFEEEEEEEEDDDDDCCBBCCCCBCCEDDCCCCBBBBBCCCBBBBBBBBBBAA@@AAAAAA@@@??@@@@?????????????????>>>=======<<<<<<<<<<<<<<<<<<<<LLLLLLLLLLLLLMLMMMMMMMMMMMNNMMMMNNNNOONNNNNNONOPOOOOOOOPOOOOOPPPPPPPPPPQQQQQQQQQRRRRRRRRRSSSSSTTSSSSSSSTTTTTUTUUUUUUTTTUUUUVVVVVWVVVVVVVVWWWWWWWWWWWXXXXXXXXXYYYYYYYYYYYYYZZZZZZZZZZZZZZZZZZZZ[[[[[[[[[[[\\\\[[[\\\\\\\\\\\\\\\]\]]]]]]]]]^]^^^^^^^^^^_______________````````a`````abaaaaaaaaaaaaaaaaabbbbbbbbbbcccccccccccccccccddddddddddddcaadedbdhdT;.*)$!%*132,%! !$&).4;;7/.3=EJNG4+,44.)-<FLNQTPJFQakbKGJOJEPURIA?AEGGECBC;201335<AB=740-,/4;BHLOQNJJJID?=?CEA9983,(*1881%!"###"#$%%&(*/2/,'"!"""$$"!$)/5:?CHIEEGGFEDEEFFFC>?CFHIIHFEDCEHIJKKJJIIHIIEBA?>=<<;:9753231.*'&%$# '@Zhnlnponmnnnnmmmmmmmmnmmmmmmmlllllllllllllllllllllllllllllllkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjkkkjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhggggggggggfggggfeeffefggggfffffffffffffeeeeeeeegfcbdeedeecbbace]SMC91)$"#&''('&)--*'),.132.*-/0+(($ ! "$$! !####$$" $+)%""! "#$$'*-,(# "()&$/62) (($" ! "(,--46.**(()/7AGJIIJIHIIKKKJIJKKJJJIIIIHHHHHHIHHHHHHHHHGGHHGGGGGGGGFFFFFFFFFFFFFFFEEEEEEEEEDDDDDCCBBCCCCCCDEEDCCCCBBBBBCCCBBBBBBBBBBAA@@AAAAAA@@@??@@@@@????????????????>>>==========<================LLLLLLLLLLLLLLLLLMMMMMMNNNNNMMMMMNNNOONNNNNNOOOOOOOOOOOOOOOOPPPOPPPPPPQQQQQQQRRRRRRRRRSSSSTTTTTSSSSSSSSTTTUUUUUUUUUUUUUUUUUVVVVVWWVVVVVVWWWXXWWWWWWXXXXXXXYYYYYYYYYZZZZZZZZZZZZZZ[Z[ZZZZZZZZ[Z[[[ZZZZ[[\[[[[[[[[\\\\\\\\\\\\\\]]]]]]]]]]]^^^^^^^^^^^^_____________``````````a`a```aaaaaa`aaaaaaaaaaaaabbbbbbbbbbcccccccccccccbbbddddddddddeeedcceeddgj`I-!"#!%+1562+%""$(-138?A>748BJNOG6///+&*6FMMNOONLGUhhWAITOLLRSNE?BDD?<;>@=7136:<?CB=744568;@EKPSUVUTSPIC>?ACC>9872--5=A;/#!%'(&%&'')+/20+)&"!$&''('&'+4=FKNNMLLKIGFEGJLMLJIHIJKLLLKKKJJKLMNNNNMLLLMLIFEDCCBA?=;96542/,+*)'%#! !! !!!! 2L`kmnnmmnnnnnmnnnnnnnnnmmnmmmmmmmmllllllllllllllllllllllllllkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjkkkkkjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihgggggggggffgggggfffffffffgggggggfffffffffffffeeeeefebcfffffebbcdaZQMRPH?5-%! !##$'+.0.+,,,.0.,,364-'(!!!! ! #""#"!!#&'&$""$%&%#! "# "%(++($ !"""!! "$('$ .74-&$(.)!& !!!!!#&+56($%"!"'0:CGHIJIGGHIKKJJIJJJJJIIIIIHHGHIIIHHHIIIIIHHHGGGGGGGHHGFFGGGFFFFFFFFEEEEEEEDDDDDDDDDCBBCCCCCCDEEDCCCCBBBBBCCCBBABBBBBBBAAAAAAAAAA@@@@@@@@@@@???????????????>>===========<<<<<<<<<<<<<<<<<LLLLLLLLLLLLLLLLLLMMMMNNNNNNMMMMNNNNOONNNNNOOOOOOPOOOOOOOOPPPPPOPPPPPQQQQQQQQRRRRRRRRSSSSSTTTTSSSSTTTSTTTTUUTTUUUUUUUUUUUUUVVVVVVVVVVVVVWWWWWWWWWWXXXXXXXXYYYYYYYYZZZZZZZZZZZZZZ[[[[[ZZZZZZZZZ[[[Z[Z[[[[[[[[\\\\\\\\\]]]]]]]]]]]]]]^^^]]^^^^^]]]^^^^^___``````____`````````aaaaa``aabaaa`aaaaaaaaaaaaabbbbbbbcccccccccccbbbbbbbcddddddddeeeeffeefedfhfW>$ #-35761+'()+.26<ADC@;:>FKKF?866/**5CMPNLKKKNS^fZOIMOHIMNMKGDB@>:9=><8659>BFHGB;6469>DJOTXYYXXWUOG@?@AA?>;88:::<@A<1)&'*-/01110.,+,($#"! "%(+-.-.16<CJORSRPNONMKKKLNPRRQRTSQPOOPQRRQPPQQRRRQPONMLLJIHGGGFDB?>;9:951..,+(%#!!"##$$%%%$#"!! !! %=Ugnomkmponnnnnnnnnnnnnmmmmmmmmmlmllllllllllllllmmmmmmmmmmmmlkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjkkkkkjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihgggggggggffgggggffgffgffgggggfgggffffffgffffffffffedbdfeddddbehe\PIKWYQH=4/+')-+)'%&+,,.-+,--/3993+&(#"! !#%$##"#'%!!&&# !%% "!#%&'((&$! !"! !"!#%('$""%(*24-*+..*#!$#"!$""&,-(''%"!%,4;ADFGGFFHIKKJIIIJJJJIIIIHHHGHIIIHIIIHIIIHHHGGGGGGHHGGGGGGGGFFFFFFFEEEEEEEDDDDDDDDDDCBCCDCCCDEDCCCCCBBBBBCCCBBBBBBBBBBAAAAAAAAAA@@@@@@@@@@@@????????@@????>====<========<<<<<<<<<<<<<<<<LLLLLLLLLLLLLLLLLLMMMNNNNNNNMMMMNNNNONNNNNONNNNOOOOOOOOOONOPQPPOPPPPQQQQQQQQRRSRRRRRRSSSSTTTTTSSSSTTTTTTTTTTUUUUUUUUUUUUUUUVWVVVVVVVVVVVWWWWWWWWWXXXXXXXXXYYYYYYYZZZZZZZZZZZZZZ[[Z[Z[[ZYZZZZ[[[[[Z[[[\[[[[[[\\\\]]]]]]]]]]]]]]]]]]^^^^]]^^^^^]]]^^^^^___``````____``a`````aaaaaaaaaaaaa`aaaaaaaaaaaaaabbbbcccccccccccccccccccbbcddddddddeeeffefffedfd]H0 *69873.**.0247=EIGC>;?EJIC<8;;6-*1@NTTOHIQOP[_[MKTQMKKKIJLLIC?=<?EA<;<?BEHLKE>:99;@GOW\ab`]YURMD><@DEC@@ABDFGEA<6/*+2447998653/)%#%'&'()+-.147:;>DINQRSTUVVURRSTTTSSUVXZ\][YWVVXYYYXWWXXXXXWUTRPNMMMLKLKIFDCB?=?>:521.,)'%%%&'(()))('%$#"""" .G^lonmoqonnnnooonnnnnnnnnnmmnnmmmmlllllllllllllmmmmmmmmmmmmlkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjkkkkjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhggggghhgggggggggggghgggggggggggggffffffffffedcdebabca[]`YRNPWcd[OA5.+*,/.+)#!'*,.-(')-26751-*)%!!!"! "&(%#! !'% !&%" #! #" #(//)$ "!!#$"#(*)"!%-232/,.12,#"" %$ " %%$#&('$! $)/49>BCEFHJJKJJJJJJJJIIIIHHHHHIIIHHHHHIIHHHHGGGGGGHHGGGGGGGGGFFFFFEEEEEEEEDDDDDDEEEDCCCDDCBCDEDCCCCCCCCCCCBCBBBBBBBBBBAAAAAAAAA@@@@@@@@@@@@@@?@????@@@@???>>===<<========<<<<<<<<<<<<<<<LLLLLLLLLLLLLLLLLLMMMNNNNNNNMMMMNNNNNOOOOOOOOOONNNNNOOPOONNPPPPOPPPQQQQQQQQQRSSSRRRRRSSTTTTTTSSSSSTTTUTTTUTTUUUUUVVVVVVVVUUVVVVVVVVVVVVVVWWWWWWXXXXXXXXXXXYYYYYYZZZZZZZZZZZZZ[[ZZZZ[[[ZZZZ[Z[[\[[Z[[[[[[[[[\\\\]]]]]]]]]]]]]]]]]]^^^^^]]^^_^^]]]^^_^^___``````____``aa````aaaaabbbaa```aabbbbbbbbbbbbbbbbccccccccccccccccccccbbcddddddddeeffedeeecce_Q9$$1==961+),268:>CILIB<<DLLF<89=7/*/;HRUSOKOWROVVQMOVQMROHGJOOKFBBCFJC=@FKJKMOK@;;>@CJS[acedb_YPJE@?BFIHHGJKLNOQJ=1**.5>BACB=731//-,,.000369:<>@DHLQUXZXWVWZ[\[XXZ\]]\\\]_acba`_^^__`_^]^_^^]\\ZYWUSQRSRQQPMIGFFECB@=963/,*)*+,,,,,++*((&%$$$$#! ! #8Qenppponnnnooooonnnnnnnnnnmmmmmnmmmmmmmmmmmmmmmmmmmmmmmmmmlkkkkkkkkkkkkkkkklllllkkkkjjjjjjjjjjjjjkkjjjjjjiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhgffffgggggggggggfffffffffgffffffffffeeebbec[MJLIKQY`ebYPB3+*/1/,*+%!&),/-(%'.35/+-0/&$%""#""$'($! !'&"!$'%"! !"#" "$$)52( #!!##"$)*)#$*1550.255/&!$" ! ## "&% "$$" $*07<?CGJKJJJJKKJIIIIIIIIIIIIIIIHHHHHHHHGGHGGGGGGHHHHGGHGGGGGFFFEEEEFFEEEDDDDDDEEEDCCDDDCBCCDDCCCCCCCCBBBBBBBABBBBBBBAAAAAAAA@@@@@@@@@@@@@@@@@@??@@@@????>==<<<<========<<=============MMMMMMMMMMMMMLLLLLMMMNNNNNNNMMMMNNNNOOOOOOOOOOOOOOONOOPPONOPPPPOPQQQQQQQQQQRRSSSRRRRRSSTTTTTTSSSSSTTUUTTUUUUUUVVUVVVVVVVVVUUVVUVVVVVVVVVVWWWWWXXXXXXXXXXYYYYYYYYZZZZZZZZZZZZZ[[[[[[[[[[ZZZ[[[[\[[[[[[[[[[[[\]]]]]]]]]]]]^^]]]]]]^^^^^^]]^^__^]]]^^_^^___``````____``aa````aaaaabbbaa```aabbbbbbbbbbbbbcccccccccccdddddddddccccccddddddddeeffecdddcacYF- !)5@?96/**.58;=CIHHHB>AIPLC;?A:-$*7ELNMLMVZTOICGQZVJKQUOGHLPNKJKMIGF@>CLSONOPJ=;@DHNXaffdb`__YLFFIMPOLLNQUSMNQQF6*)4=DFJMLF<2+*.4<CGB;::<@DGHJLOSZ^_^][[\_bb`_````aceffffeeeeeeddccbccddcbaa`_^]\ZYXZ[XWWURMJIIIGB>>;84/-,-/2310/.,+)(''&&&&%$"!!"#$# -E]kqrokmonooooooonnnnnnnnnnmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmlllllllllllllkkkklllllkkkkkkkkkkkkkkkkkkjjjjjiiiiihiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhgggggghhhhhhhhhgggggggggggggffffffghhhgegjeV@33=IRXYTNJKC4+-:90*),(%(*,00,)*240%"*1/ $!!##$&'&# !$*)&&')&#! !&('#!!$')')3/##'%!#%""##%(*)(')-00-.3;;4(!!"#"##!""!!$""#! ! %.47<CHKKKJJKKKJIIIIIIIHIIIIIHHHHHHHHHHGGGGGGGGGHHHHGGHHHGGGFFFFEEEFFEEEDDDDDDEEEEDCDDDDCBCDCCCDCCDDCCCBBBBBBBBBBBBBAAAAAAA@@@@@@@@A@@@@@@@@@@??@@@@????>>==<<<========<==============MMMMMMMMMMMMMLLLLMMMNNNNNNNNMMMMMMNNONOONNNNNNNNOPPOOOPPPOOOPPQPPQQQQQQRRRRRSRRRRRRRSSTTTTTTTSSTTTTUTTUUUUUUUUUUUUUUVVVVVVVVVVVVVVVVVVVWWWWWWWXXYYYYYYYYZYYYYYYYZZZZZZZZZZZZZ[[[[[[ZZZ[[[[[Z[[\\[[[[[[[[[\\\\]\\]]]^^^^^_^^^^^^^______^^^____^^^^_______```````````aaa````aaaaaabbaaabbbbaaabcccbbbbbccccccccddddddcccccccbbbbbccdeeeeeddeefedddddbZI3""/;BC@:-#+38:<AFHIFA?AJSUC:>ED7+$,8ENQNMPWXRJDDP^[QFLRPJFJPUTQOPRPI@AEJOQNLKIFCHNPU]flljgfd`ZTPQTTQOSZbbZMIPVVK=1.3<FLNMIB;77;BINMKIDABDGKOSVX[\^bdcbabceeedeedegijjjjllkjihhhijjiihhhgfdefffdb`_^^]]][ZYWTQNKIGDA?@>;732356752/.-,+,,+)'%$%$$$%&%$#!"5Pdnonlnommmopqpomoonnnnnnnnnnmmmmmmmmmmmmmmmmmmmmnnmmmmmmlkklllllllkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjiiiihiiiiiiiiiiihiiiiiiiiiiihiiihhggggghggggggghgggggggggggggggffffggcegbbfg]G/"+8CHJJHGSP>413562.---,*()*+.021-%&/-'!!"##$%$##$&+/.,) "$$$%&&&$#$'**'%&+13)"!!"! !#$$ !!",0,#&.352,,076.(%&&%#"!$!#&%! "% !! ""!',158>HIFIKKJIKKJJJJJJIIHIHHIIHHHHHHHHHHGHHHGHGGHHHHGGGHHGGGFFFFEEEFFEDDDEEEEDDDEDDDDCDCCCCDCCCCCCCCCCCCCCBBBBBBBBBBAAAAAAAAA@@@@@@AAAA@@@@????????????>>>=<==============<===========MMMMMMMMMMMMMLLLMMMMNNNNNNNNMMMMMMNNNNOOONNNNNOOOPPOOOPPPPOOOPQQQQQQQQRRRRRSSRRRRRSSSTTTTTTTTTTTTUUUUUUVUUUUUUUUUUUVVVVVVVVVVVWWVVVVVWWWWWWWWWXXXYYYZZZZZYYYYYYYZZZZZZZ[[[[[[ZZZZ[ZZZZ[[\[ZZ[[\\\[[[[[[[\\\\\\\]]]]^^^^____________________________```````aaaaa```aaa`a````abbbaaaaabccbcbaabccccbbbbccccccccddddddcbbbbbbcccccccdeeffedddeeddfgd^UH6&%4?BEB8*#2=>=>CHIIE?BIOSO=:FJE6)&0>KSUQRUXTIDGOY^UJEHKGDEMUWWVVURKC?FOPPOMJIFGKRZ_cglooligc^WTWYYWW\fnnfXIHSXSD838@FKNKIGBCHNSVWVPIEHLMPTW[^adfghhhgffgijihgijikmoppoopponmmmmmmnnnmmlkjijjjigecbbbcb`][XURPNLIGFEDC@=:99:::86531/--..-+(&%&%%&''%#"!)AWgmpopommnoppponopooonnnnnnnnnnmmmmmmmmmmmmmmmnnnnnnnmmlllllllllllkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjkkkkjjjjkkjjjiiiiiiiiiiiiiiiiiiiiijjjiiiiiiiihhhggggghhhhhgggggghggggggggggggggggebcfddgh`M:-(+38?IKLXXKB923763/00/*&&(+/10.,'*0*#"! #$#!!!%)-0/.+''' ".576662-)%$),,'$%-1)%'$!! $&%#!" &.0*!)0342-/240)&%&%$$" %#%)& " "# " !!!'+,+/9HKFHJJIIKKJJJJJJIIIIHHIIIHIIIIIIIHHHHHHHHHHHHHGGHHGGGGFFFFEEEFFEEDDEEEDDCCDDDDDCCCCCCCCCCCCCCCCCCDCCBCCCBBBBBBBBBBBBBAAA@@@@@AAAA@@@@???????????>>>>============================MMMMMMMMMMMMMMMMMMMMNNNNMMNNNNNNNNNMNNNNNOOONOOOOOOOOOPPPPOOPPQQQQQQQQRRRRRSSRRRRRSSSTTTTTTTTTTTTUTTTTUUUUUUUUUUUUUVVVVVVVVVVVWWVVVVWWWWWWWWWWXXXXYYYZZZZYYYYYYYZZZZZZZ[[[[[[[[[[[[ZZ[[[[[[[[[\\\[[\\\[\\\\\\\]]]]]^^^^^_______________```````````````````aaaaa```aaaa``aaaabbbaaaaabbbbccabccccccbbcccccccccddddddcccccccccccccdeeeeeddccddcchi`Q@2'!!,8@AD@1')<FD@>BHKGDCLSQKC;?MLB4)(6GSXXVWXWMABMY[TLGHEBBCHTZYXW[VLA>CNUSPNLJJKNTZakollmnkfb^\[\`]YZes}zl\PNRXUMB;;AHLNMJKPRX`ba[VRMJJS\\^adfhjlmnolllllkklllkmoppprsstttsrqrssrqqqqqqqpppponmkhgfffghfc`\XTQONMMLLLIEB????>=;989863100/.,+))))(('&#"""!"1E[krqpooopoonoopppoooonnnnnnnnnnnnnnnnnnnnnnnmnnnmmmmnmmmmmllllllllllllllllllllllllllllllllllllkkkjjjjjjkkkkjjjkkkjjjiiiiiiiiiiiiiiiiiiiiijjjiiiiiiiihhhhggghhihhhhgggggggggfffffffffghgfedefhiie[KGF8.-/8HNOSTOG=6225520/1-()+,--.-+*,/+%#####$##$+3872( "()0=DFD@;4/.+)))('(*10#$)('$"#'&"!!!#'**(%$'.0...,261+&%$"!"$#!#"#()%"&(&#"#%&" "# !!!!!!%)' &6HMJIHHHIJJHHIIIIJIJIIIIIIIIIIIIIIHHHHHHHHHHHHHGGGGGGGGFFFFFEEFEEEEDEEDDDCDDDDDDCCCCCCCCCCCCCCCCCCCCCBBCBBABBBBBBBBBBAAAA@@@@@AAAA@@@@???????????>>>>============================MMMMMMMMMMMMMMMMNNNNNNNNNNNNMMNNNMNNNNNONOOOOOOOOOOOOOPPPPPPPPQQQQQQQQRRRRSSSRRRSSSSSTTTTTTTTTTTTUUUUUUUUUUUUUUUUUUVVVVVVVVVVWWWVVVWWWWWWWWWWXXXXXXXXYYYZYYYYYYZZZZZZZZ[[[[[[[[[[[ZZZZ[\[[[[[[\\\\\\\\\\\]]]]]]]]]]^^^^^^^^^^^^^_______``````````````````aaaaaa```aaaaa``aaabbbaaaaabbbbccbccdddcccccdcccccdddddddddddddddddddddddeeedcccccddcheVA."&4>CBB;+'.AIE@=@FJDCJW]RF<;BLD7/)->MVZ\\[WNFBFNXTKHIMHCGLT_db]XUMC>ALRTOKJKMOSY_diqtrqokd^\^bfjmiemz†‚s`USV[\VMFBACGKNRXaigdc^XTSVZ^chklmopqqrrrrsrsutsrqrqrstuttttuwxxxxxwxzyxwvvvvvvvvvvusrpnlkkkllida]YVTTTTTTSQMHEDEEEB@=<<>=;854210/..-,++*(%"!"#" 0Hbnoppppqpnmnoponooooonnnnnnnnnnnnnnnnnnnnnnnnnnmmnnnmmmlmmllllllllllllllllllllllllllllllllllkkkkjjjjjjjkkjjjjkkkkjjjjiiiiiiiiiiiiiiiiiiijjjiiiiiiiihhhhhhhhiihhhhhggggggggfffgggggggggffhhfhhd^SDGPOE6++9DHEB@9351-240..10-/0.,+-,+./.+'$" $.57652,%$.653331/*$"#&)++)(*/5<<30/.)$! "''$ "" $'*+'$#',2/)***02*%"%$ !%&$! !&)($ "%&%%(*(!!$$" """! ! #%#$3ELKIIIJIHGGGHHIJJJIIIIIIIIIIIIIIIHHHHHHHHHHHHHGGGGGGGGFFFFFFFEEEEEEDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCBBBBBABBBBBBBBBBAAAAA@@@@AAAA@@@@???????????>>>>============================MMMMMMMMMMMMMMMMNNNNNNNNNNNNNNNONNNNMMNNNOOPPPPPOOOOOOPPPPPPPQQRQQQQQRRRRRSSSRRRSSSSTTTTTTTTTTTTTUTTTTUUUUUUUUUUUUUVVVVVVVVWWWWWWWWWWWWWWXXXXXXXXXXXXYYYZYYYYYZZZZZZZZZZZZZZZZZZZZZZZZ[[[[[\\\\]]]]\\\\\]]]^^]]]]]]^^^^^^^^^^^^^^______`````````````````aaaaa``````aaaaaaaaabbbbaaabbbcccccccdddcccccddcccdddddddddddddddddddddddddddddddddddecYF2$"-;ADD?6++4AEA?>?CF@DR`bRFAEILC6+*2@KOV\_\TFBJLMMKIJLLJLT^ehig`UKDCFLPRPJHINSX^ekorwxsojfb`cglnopsx…Œ‹{h\Y\^]ZOFEGKPV\`elnkhfebabfjklmrwwxzzyxwvuuvxz||{yxxyzz{zzzzzzzz{|}~~€~|{zz{{{{zzzzxwvtsrpppojedb`___^][YWRNLJJKJIEBA@@@?=;865332220--,+)%"!#$$!5Ugmoopppqomnopnmnooooooonnnnnnnnnnnnnnnnnnonnnnmmmmnnnnmmmmmmmllllllllllllllllllllllllllllllkkkkkjjjjkkkkjjjkkkkkkjjjiiiiiiiiiiiiiiiiiijjjjjjiiiiiiiihhhiiijihhhhhhhhgggghhhggggffffffcchfcda[VNACO]YC4.5>C<63,),)%),*)*-,+.0.++.-+13/-*$! *6=;6.(#!%+.253-'$#%!$(+/21.-08@GG?;972-,+' "%#!$%# "&)*'%$%(-1,&'((+*#!!%%! %'&! $()&$! !"$'+*$##" !!!$$"!" !!#'1BJKHFGHGEEFGHIIJJJJIIIIIIHHHHHHHHHHHHHHHHHHHHHGGGGGGGGFFFFFFFEEFFEEEDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBAAABBBAAAAA@@@AAAA@@@@?????????????>=============================MMMMMMMMMMMMMNNNNNNNNNNNNNNNOOOOONNNMMMNNOOPPPPPPONNOOPPPPPPPQRRQQQQRRRRRRSSSSSSSSSTTTTTTTTTTTTTTUUUTTUVVVVVVVVVVVVVVVVVVVWWWWWWWWWWWWWWXXXXXXXXXXXXXYYYZYYYYZZZZZZZZZZ[[[[[[[[[[[ZZZZ[[[[[[\\]]]]]]]]]]]]]^^]]]]]]^^^^^^^^^^^^^^^_____````````````````aaaa`aaaaaaaaaaaabbbbbbbbbbbbbbccccccddddcccccddccdddddddddddddddddddddddddddddddddddee]L7' (4?@CE<2-1:>?=>@?AD@GXc`NGHMNKD8+*5@HJS[]YQDCMPMGGJLIFKVblpkfd]SGDIQSPQPKJNU\bipuxxzwpkhhikortttwˆˆzi]Z[^]ZXMHMV_fmqqqtpihjlmptx{yvty~€€~|{{{{~‚‚€~~‚~}~€ƒ„„„„ƒ‚€~~}~~}|{{zywutrqliiiiiihfb^[WROPOONMKHFDDCA@?=;97554342.--,*&""$&%#! %D]jnoonnqqoooqonopooooooonoonnnnnnnnnnnnnooonnnnmmnnonnmmmmmmmmmmmmmmmmmmmmmmmmmlllllllllllllkkkkkjjkkkkkkjkkkkkkkjjjiiiiiiiiiiiijiiiijjjjjjjiiiiiiiiihiiiijjihhhhhhhhhhhhiihhhhgggfffcbhgcc]SQMCCKZWE<63461032-'$$%&()*)('+-.+,0-*35/.+#!&/7971-*'&'.1,*)+(# !&''/33699758>DGGA<742221-&"%$ &((%&()'$'(()*-)#%(+($ !##! !$&'#$**)$" !#%'))'#""" !#$$#! ! %'-;EJGFGHHHHHHIJJKKKKJIIIIIHHHHHHIIHHHHHGHHHHHHHGGGGGGGGFFFFFFFEEFFFEEDCCCDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBAAAAA@@AAAAA@@@?????????????>>===>>>>>>>>>>>>=============NNNNNNNNNNNNNNNNNNNNNNNNNNOONNNNONNNMMMNNOOPPPPPPOONOOPPPPPPPQRRQQQRRRRRSSSSSSSSSSTTTTTTTTTTTTTTTUUUTUVVVVVVVVVVVVVVVVVVVWWWWWWWWWWWWXXXXXXXXYYYXXXXXYYYZZZZZZZZZZZZ[[[[[[[[[[[[[[ZZZZ[[[[[[\\]]]]]]]]]]]^^^^]]]]]]^^^^^^^^^^^^^_______`````````````aaaaaaaaaabaaaaaaaabbbbbbbbbbbbbbbcccccdddddcccccdddddddddddeeeeeeeeeeeeeeeeeddddeeeeeeeedVB-!!! ##!".9@?@D9//6=;:;?A?ACAJZa[KFIMLGC;-+4AJMTYWTNHGKOOJIKGCAN^jpne^ZVQLLQUTOQRPRX_glrw{{yxsmkmswyyzz{}ƒŠ‹o^XXZYXXZ\XXcnw|~~xqnmmosxz{}€‚€€‚ƒ…††…„‚‚‚‚ƒ……………„„…‡‡…‚€‚‡‰‡„…‡ˆ‡††……„ƒƒƒ‚‚€€€€~}}|zyvuspmnooonkhc^YVSRSSROMLKJGECAA@>=;9754342.---*'$$&''$"!6Qfnpommprrpprqppppoooooooooonnnnnnnnnnnnoooonnnnnnnnnnmmmnnmmnmmmmmmmmmmmmmmmmmmmmmmmmmmmmmllkkkkkkkkkkllkkkkkkkkkjjjjjjjjjjjjjjjjiiijjjjjjjiiiiiiiiiiiiiijjiiiihhhhhhhhhhhhhhhhghhggfgjiee]OMLFEHIE=A@4*$%.:A<+(+'&*..(&')+,,-2.*24..+!$4AC7("&.0241+#%*(')1449:9:=>=<;;<;:;4+'(++**('*&"'*+*)(&$%*,+(')%!%+0*"! !#$$%&&)/.)"#&'+040) !$"!!!""#!!&*'$! !""! #"'3?HHHIKMOMIJJJKKKKKKJIIIIHHHHHHHHHHHHHGHHHHHHHGGGGGGGGFFFFFFFEEFFFEDDCCDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBAAAAAAAAAAA@@@@?????????????>>>=>>>>>>>>>>>>>=============NNNNNNNNNNNNNNNNNNNNNNNNNNOOOOOOOONNMMNNNOOPQQQPPOOOOPPPPPPPPQRRQQQRRRRRSSSSSSSSSSTTTTTTTTTTTTSTTUUUTTUVVVVVVVVVVVVVVVVVWWWWWWWWXXXXXXXXXXYYYYYYYYYYYYYYYYZZZZZZZZZ[[[[[[[[[[[[[[[[Z[[[[[[[[\\]]]]]]]]]]]]^^^]]]]]]^^^^^^^^^^_____`___``````````````a``````aaaabbbaaaabbbbbbbbbbbbbbbbccccddddddccbccddddddddddeeeeeeeeeeeeeeeeeeeeeeeeffeeed_Q=* !#$#! &2<A>>@4+.5;99<?@>?ABJX]XLFGHFB>8/-5GV]^YQLJGHJPRPJD@BIWemjb^XSPOQQPOQSQPT[dkruy{{yvpmosx}€„‡‰Š‹‡{k[TW]_`agklmr|‚ƒ~yvronrxz~€€‚‚…††‡ˆ‰‰‰ˆ‡‡‡‡ˆˆˆ‡‡‡ˆ‰‰ŠŠˆ…|†‰‰†……‰‹‹‰ˆˆˆ‡†††††„„ƒ‚ƒ„ƒ‚€}}{yxwsqqqpomjgc^[YYXXVTPOOOMJEBCCBA?=:864331/.--+)'&()(%#")B[kttqoprrpopqqpppppppoooooonnnnnnnnnnnnoooooonnnnnnnnnnnnnmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmllkkkkkkkkkklkkkkkkkkkkjjjjjjjjjjjjjjjjiijjjjjjjjjjijjiiiiiiiiiiiiiiiiihhhhhhhgghhhhghhhhhhhfdbbWE@?==?ACGQR@0%"%,/1146-)-11**.21+,/3/*02,.,!#.:A?1%%,43463.+%!&'$&-9>=;99;==94/,,+-4.$"#&%""$&&$ !&)+*)'#%+,+*'&'#&/5,!! %(((()-1.'#(*,2780%!&&"!"$')'##(*(&$###$#!"(%$+8DIKMLKJJKKKKKKKKKJJJJIIHIIIIIHHHHHHHGHHHHHHHGGGGGGGGFFFFFFFEEEEEEEDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCDDDDCCCCCCCCCBBBBABBAAAAAAAAAA@@@@????????@????>>>=>>>>>>>>>>>>>>============NNNNNNNNNNNNNNNNNNNNNNNNOOOOOOOOOOONNNNOOOOPPQQPPPOOPPQQQQPPPQRRQQRRRRRRSSSSSTTTTTTTTTTTTTTTTTTTTUTTTTVVVVVVVVVVVVVVVVVWWWWWWWWWXXXXXXXXXYYYYYYYYYYYYYYYYYZZZZZZZZ[[[[[[[[[[[[[[[[[[[[\\\\[[\\]]]]]]]]]]]]^^^^^^^^^^]^^^]^^^______``````````````````aaaaaaaaaaabbbaaaabbcbbbbbbbbbbbbccccddddddccbbccdddddddddeeeeeeeeeeeeeeeeeeeeeeeefffedd`XI7'#&$" !$+5<@?<8/),3977;=>?>=?IWYUMHGGGD?944=ScgbWKEEDGLQSQI@@JXbgh`WVTOMNQPNMQXRPZentxz{{yvtqrv{€‚„‡Œ‘Ž†zmd\Z_eklotz}|‚††ƒ}yxy|}‚„††…††‡…†‰Š‹ŒŒŒŒŒŒŒŒŒŒŠŠ‹ŽŽŽŒŠ…~…ˆŠ‰‰ŠŒ‹‹ŠŠŠ‰‰‰‰ˆ‡‡……†…„ƒ€~}|{zwtsrqolifda_^_^\YVSQRRPLHEFFECA?=:7543210.-,+))**)&%#/F_pusppqronooppppppppopoooooooooooooooooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmmmmmllllllllllmllkkkkkkkkkklkkkkkkkkkkjjjjjjjjjjjjjjjjiijjjjjjjjjiijjjiiihhiiiiiiiiiiiiiihhhhhhgggggghgggiheca_SA:89:934=ED6-(&&(,0376-(*-.)+053-.141-00+--%-;;84,'*/2..2/,.)#&'%)1::5345653/*'&),160'%%&" #&$#""! "'**)(&"(0,('&%$! (/2(!'++**,/1+" %)*-1661)##&(&$#$&*-.*%#$'**'&&')('# $! +8?FKLKKKKKKKKKKKKJJJJIIHIIIIIIIHHHHHHHHHHHHHGGFFFFFFFFFFFFFEEEEEEEEDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCDDDDCCCCCCCCCCCCBBBBBAAAAAAAAA@@@@???????@@@????>>=>>>>>>>>>>>>>=============NNNNNNNNNNNNNMMMNNNNOOOOOONNNNNNNNNOOONOOOOPPPPPPPPOPPQQQQPPPQRRQQRRRRRSSSSSSTTTTTTTTTTTTTTTTTTTTUUUTTVVVVVVVVVVVVVVVVWWWWWWWWWWXXXXXXXXYYYYYYYZZZZZZZYYYYZZZZZZZ[ZZZZZZZZZZZZZZZZZZ[[\\\\[[\\]]]]]]]]]]]]^^^^^^^^^]]]]]]^______````aa```````````````````aaaaaabbbaaaabcccbbbbbbbbbbcccccddddddcbbbccddddddddeeeeeeeeeeeeeeeeeeeeeeeefffedbc]QA0#$&" !#)05;?B;/++,39768:>B>9>JXXQKGGHIH@:9>K^if]PFBCBGNRROGBIWdgc^WQONMMMNORUWYTVcnvxyyyxwvvx|…ˆˆ‹Ž”•‡xh__dnx|}}€„‡ˆˆŠŠ†„ƒ‚‚„ˆŒŒŠ‡‡‡‰‰ŠŒŽŽ‹‹Ž‘‘‘‘‘‘ŽŽ’“’‘‘‹†…‡‰‹ŽŒ‹‹‹‹ŠŠ‰ˆ‡‡†…ƒƒ€~}|zxwvtqnjhfddcca_[XWUTSQOMKJHGECA?<:865542/.-,++,,*'&% 1Meqspoqspnoooppppppppppoooooooooooooooooooooooooonnnonnnnnnnnnnnnnnnnnnnnnnnnmmmmmmmmmmmmllllkkkkkkkllkkkkkklkkkjjjjjjjjjjjjjjjjiijjjjjjjjjiijjjjihhhhhhhhiiiiiiiiiihhhiiihggfffeeeffcb`ZNA96751'%,-+'),+**.122/)&&'('*.230245312/*./+5A920-+,--'&**+-&&+*,040)+/0/,)(()*-1451,)'%!#')$"#"! $)+*&&%"*2)##$$! #,.( !##"'++**/20''+,*0781'"#*-,%#(),01/*'(*+'()(()))% (,4<AEJKKKKKJJJJJJJJJIIHHIIIIIHHHHHHGHHHHHHHGFFFFFFFFGGGGGGFEEEDDEEDDDCDDDDDDCCCCCCCDDDDDDDDDDDDCCCCCCCBBBBBBBCCCCBBBAAAAAAAAAA@@@@??????@@@@@???>>=>>>>>>>>>>>>>>>>>>>>>>>>>>NNNNNNNNNNNNMMMMNNNNOOOOOOOONNNNNNNOOOOOOOOPPPPPPPPOOPQQQQPPPQRRQQRRRRRSSSSSTTTTTTTTTTTTTTTTTTTTTUUUTTUVVVVVVVVVVVVVVWWWWWWWWWXXXXXXXXXYYYYYYZZZZZ[[ZZZZYYZZZZZZZ[[[[[[Z[[[[[[[[[ZZZ[[\\\\[[\\]]]]]]]\]]]]^^^^^^^^^]]]]]]^_____```aaaaaaaaaaaaaaaa`aaaaa`aaaaaabbbaaaabccccbbbbbbbbbbcccdddddddcbbbccddddddddeeeeeeeeeeeeeeeeeeefeeeefffdcab[L:)#%!%-359?C;)*/16<866:@G@7@P]XMDBDFHH?9=HWdeYPHDCDFHMPPKHJVagbZTRQIGKMNLS]c`UWbnwzxvuuvwy|‚‡ŠŽ’”•”’„woot}†Œ‹‹ŠŠ‹‹Ž‘’’““ŽŒ‹Š‹ŒŒŽ‘““‘’•”’“““””•–——˜—•“‘’”––”““”“’‘ŒŽ‘““’‘‘’’ŽŽŒŒŠˆ‡‡††…ƒ‚€~}||{zxuqnkhgffec`]\[YVSQQSQLIGFDB?=<:988730.--,,,,+('%!! ";Wjpomptrpqqqqqppppppppppppppppppppppppoooppppponnnoonnnnnnnnnnnnnnnnnnnnnnnnnmmmmmmmmmmnmlllkkkkkkkllkkkkkklkkkjjjjkkkkkkkkkkkkjjjjjjjjjjjjjjjjiihhhhhhhijjiiiiiiiiiiijjjhgfeedcbbcca_[RF=621+%&'&! ',-*)+**,+('''&&')++/36765440+/226:7793,''($!"+0+ &//+),+)*,-+)(,021-)'**(%" !&*+%!"! %+-*%$$$+0%"#" &0-(,*$%()((36/#%*++,5>;0#"&-.+&'-/142-('+-+"'/+%"!!#-7AIKKJJJIIJJJJKJIIHIIIIIIIHHHHHHHHHHHHHGGFFFFFFFGGGGGGFEEDDDEEEEDCDDDDDDCCCCCCCCCCCCCCCCCCCCCBBBBCBBBBBBBBCCCBBBAAAAAAAAAA@@@@?????@@@@@@???>>=>>>>>>>>>>>>>>>>>>>>>>>>>>NNNNNNMMMMMMMNNNNNNNNNNMMNNNNNNNOOOOPPONOOOPPPPPQQQQQPPPPQQQQQQQQRRRRRRSSSSTTTTTTTTSSSSSSSSSTTTTTUTTTTUVVVVVVVVVVVVVVWVWWWWWXXXXXXXXXXXYYYYYYYYYYYYYYYYYYYZZZZZZZ[[[ZZ[[[Z[[[[Z[[[\[[\\\\\\\\\\]]]\]\\\]\]]^^^^^^^^^^^^^^^^____```````__`````aaaaaaaaaaabbbbbbbaabbbbbbccccbcddddddddddeedccdddddddcccdddddddeeeeeeeeeeeeeeeeeeedefededdddc^RA2& "!!'.137?C=2)-57;><99?EJ?6L]_UIDCDDB??BDQ_^UJLOLGCGKKFCELU_ec[TSPLIHIMS^dc]Y\dnsuuttuuvz‰‘••—™›™”…~wwƒŒ’—˜–“‘‘‘’•–•••–––˜˜–”’’“““““”””•————————˜˜˜˜˜˜——˜˜—–••––•––”“’’’’’“’’’“““‘ŒŒŒŠ‰ˆ‡‡†…ƒ~~}||{yuqomkjhfedb`][YWWVUTRLIIGEDBA?=;976410/--,++**(&! $$ &@]msppqqqpqqppppqqqqqpppppppqqppopppqpoooppppooonnnnnnnmnnnnnmnnnnnnnnnnnnnnnmmmmmmmmmmmmlllkkkklllllllllkkkkkjjkkjjjjjjjjjjjjikkkkkkkkkkkkkjjiiihhhiiiijlkjiijjihhhiihiiimnigfeegkkf\PE8-**)%$))"#',+'%$$$$&(*./-($$&('+35435882+2957951/,)(('$ .5*" *0/*&)***)'),0340(""%&$!!#)-/-$!&"!(,,,+)" &,.$" #)1+$)))'#$+*(66' &)&$'09<60,-121-&&-694.)++(()*./' !%'5EJLLJHHJKJJJJIIIHHHHHHHIHHHHHHHHHHHHGGFFFFGGGGGFFFFGFEDDDDDDDDCCDDCCCCDDDDDDCCCCCCCCCCCCCDDCCCCBBBBBCCCBBBBBAAA@@@@AAAAAA@@@@@@@@@@@@@??????>>>>>>>>=>>>==============NNNNNNMMMMMLMOOONMNNNNMLLMNNNOOOOOPOPPONOOOOPPPPQQQQQPPPPQQRQQPPRRRRRRRSSSTTTSSSSSTSSSSSSSSSTTTTTTUUUUVVVVVVVVVVVVVVVVVVWWWXXXXXXXXXXXXYYYYYYYYYYYXXYYYYYYZZZZZZZ[ZZZZ[[[ZZZZZ[[[\\\\\\\\\\\\\\]]]\\\[\\\\]^^^^^^^^^______^____```_______`__``a``abaaaaabbbbbbbaabbbbbbbbbbbccccccccddddedcccdddddddccdddddddeeeeeeeeeeeeeeeeeeedeeddeddeebXI9-%!##!!$*003<DD6,,07;@C><?EHF=:P^\RIHGEB>=DJKS[TLFLRPHAEGD@AIT_ee_XTWRJIILR[efb\]gnsvwvwz}…•›žœš”‡……Š•œž žš–••—™™™™šœœš™™™š›œš˜˜™™˜—––—˜™šš›ššššš››šš™™˜šœœ™–•–——–˜˜••••”“““““““””“‘‘‘‘‘ŽŒŒŠ‰ˆˆˆ‡…‚€~}}{xtqpnmkigfeda^[YXYYVURMJJHFEDBA?<:875320.-,,+++)&""&%"0Ohusqpqqpqqpppqqqqqqqpppoppqqpoooppqpooooooooooooooonnnnnnnnnnnnnnnnnmmmmmmmmmmmmmmmlllllllllllllllllmmmlkkkkjkkkjijjjjkjjjjjijjkkkkkkkkkjjjiiiiihiiiijjkjiiijkihghiiihjmlg^ZY[_dhdWG:5,""%&''*'"')&""""#%%&)-.-)%#$&%)12259<<707<88973/+))(%! "06+&&+-,(%'()'&&).487/$!%% )111-# # ,.+*)' '*'" " %*.'!&'(**%$,+)42#"))##*7<;6568752/')3::0+*-+$#(/-&!"" %3>HMNKIIIIIIIIIIHHHHHHHIHHHIIIIIIHGHGGFFFGGGGGGGFFFGFEEDDDEDDDDDCCCDDDDDDDDDCCCCCCCCCCCCCDDCCCCBCCCCCCCCCBABAAAA@@@AAAAAA@@@@@@@@@@@@@???????>>>>>>=======<<<<<<<<<<<<NNNNNNNNNMMMMNONNMNNNNMMMMNNNNNNNOOOOOOOOPOOPPPPPPQQQPPPPQQRRQPPRRRRRRRSSSTTTSSSSSTTTTTTTTTTTTTUUUUUVVVVVVVVVVVVVVVVVVVWWWWXXXXXXXXXXXYYYYYYYYYYYYYYYYYZZZZZZZZZZ[[[[[[Z[[[[Z[[\[\\\\\\\\\\\\\]]]]\\\[\\\]]^^^^^^^^^^^^^_______``````````````aaaaaaaaaabbbbbbbbbbbbbbbbbbbbccbbbbbbbbcccdddddccccccddddddddddeeeeeeeeeeeeeeeeeeeeeedefeefd^QB5*$" #$#!"$'*+.6BF@3,027>CE==EJI><BNVWQKMKE@?BJONOOLKLOPLF@@>=AIS^ffaZWWWRKHJQ[a`]\dlrsu{‚†‹’”˜œžžœ›šš—•““•™ ¢££¢Ÿœž ¡ ¡¡¡ žžžžžœœ›››œœ›››››œš˜———˜˜™™—–––•””•””””“““”“’’’’‘ŽŒŒŒŒŠ‰‰‰ˆ†„‚€€~}zwtrponlihgfdb_\ZYXWVUSNLJHFDCBA?=<:976310/.--,,*'$$'&#'@]rttrtsprsqqqqqqqqqqpppoppqqpoooppqqpppooooooooooooonnnnnnnnnnnnnnnmnnnnnnnnnnnnnmmmmmmmmmmmmmllllllmmllkkkkkkkkjjkkklllkkkkjiijjjjjjjjjiiiiiiiiiiiiiiiihhhijjjhhhijkiih^SLFELU[YN;,&+*"#'+,+*%!"""#%&()))))))(&$#$'*..08<<>=:<<978764.*(%"%/3,)'''''&&&'$%*,.389/%"%$ '/531,%,-((& !'%!#! "%)*##&+..(&*++/,$ ')(%(09<:99:97420-1<;3+*-+& #)-%"&)(" $$# ! "-;HOPLHFHIJJIIIIIIIIIIIIIIIIIIIHHHHGGFFGGGGGGGGGFFGGFEEDEEEEDDDCCDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCBBBBBBCBBBBAAAAA@@AAAAAA@@@@@@@@@@@@@???????>>>>>>=======<<<<<<<<<<<<NNNNNNNNNMMMMNNNNNNNNNMMMNNNNNNNNNOOOOOOOOOOOPPPPPPQQPPPPPQRRQQQRRRRRRRSSSTUTSSSSSTTTTTTTTTTTTTUUUUUVVVVVVVVVVVVVVVVVVWWWWWXXXXXXXXXXYYYYYYYYYYYYYZZZZZ[[ZYYYYZ[ZZZZZZZZZZZZ[[[[\\\\\\\\\\\\\]]]]]\\\[\\\]]^^^^^^^^^^^^^^^___````````````````aaaaaaaaabbbbbbbbbbbbbbbbbbbccccbbbbbbbbbcccddddccccccddddddddeeeeeeeeeeeeeeeeeeeeefeeeeffefcYI9-%! !"" "%'('.:HH=0-679@C@9<INI79FRWUOLPLD>>ELPPMIJMQROJE@;9;CNYdje[RQRPLJKNT[_YW]my{|~…Š“™œžžŸŸœ—’‘”—›Ÿ¢¥§¨¨¨¦¤£££¤¤¥¦¦¥¦¦¥£¢¡¡¢¢£¢¡ ŸžžžžžŸ ¡ ŸŸžžœžžœš™™™ššš›™˜—–––——–––””“”––”“““”’ŒŒ‹Š‰ˆ†„‚€€}zwtrrqonkiigec`^\ZXVVVTPMKIGEDCB@?=<;:85310//..-+(''(&# /Nisustsprsrqrrrrrqqqqppopppppppoppppppppppooooooppppoooooooooonnnnmmmmmmmmmmmmmmmmmmllmmmmmllmllllllmlllllkkllllkkkllllllllkkjjjjjjjjjjjjjhhijjjjjjiihhhhghiiiiiiihijj_KCCKF?HQSE4)$&/1,,./.,+($$%&()+*))+/1342.*&"!#(,/++48:>CEA:41/.-+&#$##*-**(#"%))%%&"&...277-#!#"#+4730-("#/.''$ $'"!!$(&!!&140)$(*++(##*)'&,1/2542023202;A@90(')$!&*'"%&(''!#%''%" !"*6DMOLGGIMLJJJIIIHHIIIIIHIIIIIIHHHHGGGGGHGGGHGGGGGGFEEEEEEEEDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCBCCCCBBBBBBAAAAAAAAAAAA@@@@@@@@@@@@@???????>>>>>>=======<===========NNNONNNNNNNNNNNNNNNNNNNNNMMMNNNNNONOOOOOOOOOPPPPPPPQQPPPPPQRRQQQRRRRRRRSSSTUTSSSSSTTTTTTTTTTUUUUUVVVVVVVVVVVVVVVVVVVVWWWWWXXXXXXXXXXYYYYYYYYYYYYYZZZZZZ[[[ZZZZZ[[[[[[[ZZZZ[Z[[\\\]]\\\\\\\\]]]]]]]\\\[\\\]]^^^^^^^^^^^^]]^___``````````````````aabbaaabbbbbbbbbbbbbbbbbbccccccccccccccdddddddccccccdddddddeeeeeeeeeeeeeeeeeeeeeeffefggfdd^SB2' #%')3@ID7/0:;=BA;7<HKD6;KVZTKHMIB?CLOPNKIKORPKGB>88@HR]fh_SKJLHGIRY[^__ckx~}}‚‰Ž’”˜Ÿ ŸŸ“ŽŽ’—œ¡¥¨ª¯®«©©©©ªª©©ªªª«ª©¨¦¥¦§§§§¥¤¤££¢¢¡¡ ¡¢¢¡¡¢¢£¢¡ ŸžžžŸ Ÿžœœœœ››››šš™˜—˜˜™™—–•””•——–••••“ŽŽŽŽŽŒŠŠ‰ˆ†„}zwutsrqomkjifdb`^[XVUVUQOMKIGEDCB@?>=<:753110/..,*))('$" ;Yltssrqrsrrrrrrrrrqqqppppppppppppppqqpppppoppppppppooooooooonnnnmmmmmmmmmnnnnnmmmmmmmmmmmmmmmllllllllllllkklllllkklmmmmmllkkkkkkkkkkkkkkkiijjjjjjjjjiiihhiiiiiijihgf`SFOZZQHIC5/+'&(.0-+****(&%$%%'))'#"$&+27961,'$#"(3.+37:?EHA82-)(&$"!"!#&()*)! %++#!##)1.+043)"!! &-230/.,& &.,&%""&& "(% !+9;0&"%'''&##)'&'-1-151+&,2301?E?5+'%#")*"!%%%')(# diff --git a/media/testdata/faces_I400.jpg b/media/testdata/faces_I400.jpg Binary files differnew file mode 100644 index 0000000..c928f00 --- /dev/null +++ b/media/testdata/faces_I400.jpg diff --git a/media/testdata/faces_I411.jpg b/media/testdata/faces_I411.jpg Binary files differnew file mode 100644 index 0000000..600c122 --- /dev/null +++ b/media/testdata/faces_I411.jpg diff --git a/media/testdata/faces_I420.jpg b/media/testdata/faces_I420.jpg Binary files differnew file mode 100644 index 0000000..10c5332 --- /dev/null +++ b/media/testdata/faces_I420.jpg diff --git a/media/testdata/faces_I422.jpg b/media/testdata/faces_I422.jpg Binary files differnew file mode 100644 index 0000000..9907aa1 --- /dev/null +++ b/media/testdata/faces_I422.jpg diff --git a/media/testdata/faces_I444.jpg b/media/testdata/faces_I444.jpg Binary files differnew file mode 100644 index 0000000..3422755 --- /dev/null +++ b/media/testdata/faces_I444.jpg diff --git a/media/webrtc/OWNERS b/media/webrtc/OWNERS index 9a3546e..aef5939 100644 --- a/media/webrtc/OWNERS +++ b/media/webrtc/OWNERS @@ -1,3 +1,3 @@ mflodman@webrtc.org +pbos@webrtc.org pthatcher@webrtc.org -wu@webrtc.org diff --git a/media/webrtc/fakewebrtcvideocapturemodule.h b/media/webrtc/fakewebrtcvideocapturemodule.h index 347e4b7..82b5cdd 100644 --- a/media/webrtc/fakewebrtcvideocapturemodule.h +++ b/media/webrtc/fakewebrtcvideocapturemodule.h @@ -44,76 +44,72 @@ class FakeWebRtcVideoCaptureModule : public webrtc::VideoCaptureModule { running_(false), delay_(0) { } - virtual int32_t Version(char* version, - uint32_t& remaining_buffer_in_bytes, - uint32_t& position) const { + virtual int32_t TimeUntilNextProcess() OVERRIDE { return 0; } - virtual int32_t TimeUntilNextProcess() { + virtual int32_t Process() OVERRIDE { return 0; } - virtual int32_t Process() { - return 0; - } - virtual int32_t ChangeUniqueId(const int32_t id) { + virtual int32_t ChangeUniqueId(const int32_t id) OVERRIDE { id_ = id; return 0; } virtual void RegisterCaptureDataCallback( - webrtc::VideoCaptureDataCallback& callback) { + webrtc::VideoCaptureDataCallback& callback) OVERRIDE { callback_ = &callback; } - virtual void DeRegisterCaptureDataCallback() { callback_ = NULL; } - virtual void RegisterCaptureCallback(webrtc::VideoCaptureFeedBack& callback) { + virtual void DeRegisterCaptureDataCallback() OVERRIDE { callback_ = NULL; } + virtual void RegisterCaptureCallback( + webrtc::VideoCaptureFeedBack& callback) OVERRIDE { // Not implemented. } - virtual void DeRegisterCaptureCallback() { + virtual void DeRegisterCaptureCallback() OVERRIDE { // Not implemented. } - virtual void SetCaptureDelay(int32_t delay) { delay_ = delay; } - virtual int32_t CaptureDelay() { return delay_; } - virtual void EnableFrameRateCallback(const bool enable) { + virtual void SetCaptureDelay(int32_t delay) OVERRIDE { delay_ = delay; } + virtual int32_t CaptureDelay() OVERRIDE { return delay_; } + virtual void EnableFrameRateCallback(const bool enable) OVERRIDE { // not implemented } - virtual void EnableNoPictureAlarm(const bool enable) { + virtual void EnableNoPictureAlarm(const bool enable) OVERRIDE { // not implemented } virtual int32_t StartCapture( - const webrtc::VideoCaptureCapability& cap) { + const webrtc::VideoCaptureCapability& cap) OVERRIDE { if (running_) return -1; cap_ = cap; running_ = true; return 0; } - virtual int32_t StopCapture() { + virtual int32_t StopCapture() OVERRIDE { running_ = false; return 0; } - virtual const char* CurrentDeviceName() const { + virtual const char* CurrentDeviceName() const OVERRIDE { return NULL; // not implemented } - virtual bool CaptureStarted() { + virtual bool CaptureStarted() OVERRIDE { return running_; } virtual int32_t CaptureSettings( - webrtc::VideoCaptureCapability& settings) { + webrtc::VideoCaptureCapability& settings) OVERRIDE { if (!running_) return -1; settings = cap_; return 0; } virtual int32_t SetCaptureRotation( - webrtc::VideoCaptureRotation rotation) { + webrtc::VideoCaptureRotation rotation) OVERRIDE { return -1; // not implemented } virtual VideoCaptureEncodeInterface* GetEncodeInterface( - const webrtc::VideoCodec& codec) { + const webrtc::VideoCodec& codec) OVERRIDE { return NULL; // not implemented } - virtual int32_t AddRef() { + virtual int32_t AddRef() OVERRIDE { return 0; } - virtual int32_t Release() { + virtual int32_t Release() OVERRIDE { delete this; return 0; } diff --git a/media/webrtc/fakewebrtcvideoengine.h b/media/webrtc/fakewebrtcvideoengine.h index 5cba380..ec4b9c6 100644 --- a/media/webrtc/fakewebrtcvideoengine.h +++ b/media/webrtc/fakewebrtcvideoengine.h @@ -335,6 +335,7 @@ class FakeWebRtcVideoEngine unsigned int send_nack_bitrate_; unsigned int send_bandwidth_; unsigned int receive_bandwidth_; + // Bandwidth to deduct from estimated uplink capacity. unsigned int reserved_transmit_bitrate_bps_; bool suspend_below_min_bitrate_; webrtc::CpuOveruseObserver* overuse_observer_; diff --git a/media/webrtc/fakewebrtcvoiceengine.h b/media/webrtc/fakewebrtcvoiceengine.h index f731b8d..52a50ff 100644 --- a/media/webrtc/fakewebrtcvoiceengine.h +++ b/media/webrtc/fakewebrtcvoiceengine.h @@ -43,10 +43,7 @@ #ifdef USE_WEBRTC_DEV_BRANCH #include "webrtc/modules/audio_processing/include/audio_processing.h" #endif - -namespace webrtc { -class ViENetwork; -} +#include "webrtc/video_engine/include/vie_network.h" namespace cricket { @@ -64,6 +61,12 @@ static const int kFakeDeviceId = 0; static const int kFakeDeviceId = 1; #endif +static const int kOpusBandwidthNb = 4000; +static const int kOpusBandwidthMb = 6000; +static const int kOpusBandwidthWb = 8000; +static const int kOpusBandwidthSwb = 12000; +static const int kOpusBandwidthFb = 20000; + // Verify the header extension ID, if enabled, is within the bounds specified in // [RFC5285]: 1-14 inclusive. #define WEBRTC_CHECK_HEADER_EXTENSION_ID(enable, id) \ @@ -183,6 +186,7 @@ class FakeWebRtcVoiceEngine file(false), vad(false), codec_fec(false), + max_encoding_bandwidth(0), red(false), nack(false), media_processor_registered(false), @@ -212,6 +216,7 @@ class FakeWebRtcVoiceEngine bool file; bool vad; bool codec_fec; + int max_encoding_bandwidth; bool red; bool nack; bool media_processor_registered; @@ -308,6 +313,9 @@ class FakeWebRtcVoiceEngine bool GetCodecFEC(int channel) { return channels_[channel]->codec_fec; } + int GetMaxEncodingBandwidth(int channel) { + return channels_[channel]->max_encoding_bandwidth; + } bool GetNACK(int channel) { return channels_[channel]->nack; } @@ -316,6 +324,8 @@ class FakeWebRtcVoiceEngine } webrtc::ViENetwork* GetViENetwork(int channel) { WEBRTC_ASSERT_CHANNEL(channel); + // WARNING: This pointer is for verification purposes only. Calling + // functions on it may result in undefined behavior! return channels_[channel]->vie_network; } int GetVideoChannel(int channel) { @@ -488,8 +498,6 @@ class FakeWebRtcVoiceEngine WEBRTC_STUB(LastError, ()); WEBRTC_STUB(SetOnHoldStatus, (int, bool, webrtc::OnHoldModes)); WEBRTC_STUB(GetOnHoldStatus, (int, bool&, webrtc::OnHoldModes&)); - WEBRTC_STUB(SetNetEQPlayoutMode, (int, webrtc::NetEqModes)); - WEBRTC_STUB(GetNetEQPlayoutMode, (int, webrtc::NetEqModes&)); // webrtc::VoECodec WEBRTC_FUNC(NumOfCodecs, ()) { @@ -625,10 +633,11 @@ class FakeWebRtcVoiceEngine } WEBRTC_STUB(GetVADStatus, (int channel, bool& enabled, webrtc::VadModes& mode, bool& disabledDTX)); + #ifdef USE_WEBRTC_DEV_BRANCH WEBRTC_FUNC(SetFECStatus, (int channel, bool enable)) { WEBRTC_CHECK_CHANNEL(channel); - if (strcmp(channels_[channel]->send_codec.plname, "opus")) { + if (_stricmp(channels_[channel]->send_codec.plname, "opus") != 0) { // Return -1 if current send codec is not Opus. // TODO(minyue): Excludes other codecs if they support inband FEC. return -1; @@ -641,6 +650,25 @@ class FakeWebRtcVoiceEngine enable = channels_[channel]->codec_fec; return 0; } + + WEBRTC_FUNC(SetOpusMaxPlaybackRate, (int channel, int frequency_hz)) { + WEBRTC_CHECK_CHANNEL(channel); + if (_stricmp(channels_[channel]->send_codec.plname, "opus") != 0) { + // Return -1 if current send codec is not Opus. + return -1; + } + if (frequency_hz <= 8000) + channels_[channel]->max_encoding_bandwidth = kOpusBandwidthNb; + else if (frequency_hz <= 12000) + channels_[channel]->max_encoding_bandwidth = kOpusBandwidthMb; + else if (frequency_hz <= 16000) + channels_[channel]->max_encoding_bandwidth = kOpusBandwidthWb; + else if (frequency_hz <= 24000) + channels_[channel]->max_encoding_bandwidth = kOpusBandwidthSwb; + else + channels_[channel]->max_encoding_bandwidth = kOpusBandwidthFb; + return 0; + } #endif // USE_WEBRTC_DEV_BRANCH // webrtc::VoEDtmf @@ -999,6 +1027,11 @@ class FakeWebRtcVoiceEngine WEBRTC_CHECK_CHANNEL(channel); channels_[channel]->vie_network = vie_network; channels_[channel]->video_channel = video_channel; + if (vie_network) { + // The interface is released here to avoid leaks. A test should not + // attempt to call functions on the interface stored in the channel. + vie_network->Release(); + } return 0; } diff --git a/media/webrtc/webrtcmediaengine.cc b/media/webrtc/webrtcmediaengine.cc index 252b4e6..cf0fcdf 100644 --- a/media/webrtc/webrtcmediaengine.cc +++ b/media/webrtc/webrtcmediaengine.cc @@ -50,7 +50,6 @@ class WebRtcMediaEngine : WebRtcVideoDecoderFactory* decoder_factory) { voice_.SetAudioDeviceModule(adm, adm_sc); video_.SetVoiceEngine(&voice_); - video_.EnableTimedRender(); video_.SetExternalEncoderFactory(encoder_factory); video_.SetExternalDecoderFactory(decoder_factory); } @@ -65,8 +64,9 @@ class WebRtcMediaEngine2 : WebRtcVideoEncoderFactory* encoder_factory, WebRtcVideoDecoderFactory* decoder_factory) { voice_.SetAudioDeviceModule(adm, adm_sc); + video_.SetExternalDecoderFactory(decoder_factory); + video_.SetExternalEncoderFactory(encoder_factory); video_.SetVoiceEngine(&voice_); - video_.EnableTimedRender(); } }; #endif // WEBRTC_CHROMIUM_BUILD @@ -85,7 +85,6 @@ cricket::MediaEngineInterface* CreateWebRtcMediaEngine( adm, adm_sc, encoder_factory, decoder_factory); } #endif // WEBRTC_CHROMIUM_BUILD - // This is just to get a diff to run pulse. return new cricket::WebRtcMediaEngine( adm, adm_sc, encoder_factory, decoder_factory); } diff --git a/media/webrtc/webrtcmediaengine.h b/media/webrtc/webrtcmediaengine.h index b906f5d..df517ee 100644 --- a/media/webrtc/webrtcmediaengine.h +++ b/media/webrtc/webrtcmediaengine.h @@ -122,9 +122,6 @@ class DelegatingWebRtcMediaEngine : public cricket::MediaEngineInterface { virtual bool SetAudioOptions(const AudioOptions& options) OVERRIDE { return delegate_->SetAudioOptions(options); } - virtual bool SetVideoOptions(const VideoOptions& options) OVERRIDE { - return delegate_->SetVideoOptions(options); - } virtual bool SetAudioDelayOffset(int offset) OVERRIDE { return delegate_->SetAudioDelayOffset(offset); } @@ -151,9 +148,6 @@ class DelegatingWebRtcMediaEngine : public cricket::MediaEngineInterface { virtual bool SetLocalMonitor(bool enable) OVERRIDE { return delegate_->SetLocalMonitor(enable); } - virtual bool SetLocalRenderer(VideoRenderer* renderer) OVERRIDE { - return delegate_->SetLocalRenderer(renderer); - } virtual const std::vector<AudioCodec>& audio_codecs() OVERRIDE { return delegate_->audio_codecs(); } diff --git a/media/webrtc/webrtcpassthroughrender.h b/media/webrtc/webrtcpassthroughrender.h index a432776..8d8c488 100644 --- a/media/webrtc/webrtcpassthroughrender.h +++ b/media/webrtc/webrtcpassthroughrender.h @@ -41,26 +41,20 @@ class WebRtcPassthroughRender : public webrtc::VideoRender { WebRtcPassthroughRender(); virtual ~WebRtcPassthroughRender(); - virtual int32_t Version(int8_t* version, - uint32_t& remainingBufferInBytes, - uint32_t& position) const { + virtual int32_t ChangeUniqueId(const int32_t id) OVERRIDE { return 0; } - virtual int32_t ChangeUniqueId(const int32_t id) { - return 0; - } - - virtual int32_t TimeUntilNextProcess() { return 0; } + virtual int32_t TimeUntilNextProcess() OVERRIDE { return 0; } - virtual int32_t Process() { return 0; } + virtual int32_t Process() OVERRIDE { return 0; } - virtual void* Window() { + virtual void* Window() OVERRIDE { rtc::CritScope cs(&render_critical_); return window_; } - virtual int32_t ChangeWindow(void* window) { + virtual int32_t ChangeWindow(void* window) OVERRIDE { rtc::CritScope cs(&render_critical_); window_ = window; return 0; @@ -70,64 +64,60 @@ class WebRtcPassthroughRender : public webrtc::VideoRender { const uint32_t stream_id, const uint32_t zOrder, const float left, const float top, - const float right, const float bottom); + const float right, const float bottom) OVERRIDE; - virtual int32_t DeleteIncomingRenderStream(const uint32_t stream_id); + virtual int32_t DeleteIncomingRenderStream(const uint32_t stream_id) OVERRIDE; virtual int32_t AddExternalRenderCallback( const uint32_t stream_id, - webrtc::VideoRenderCallback* render_object); + webrtc::VideoRenderCallback* render_object) OVERRIDE; virtual int32_t GetIncomingRenderStreamProperties( const uint32_t stream_id, uint32_t& zOrder, float& left, float& top, - float& right, float& bottom) const { + float& right, float& bottom) const OVERRIDE { return -1; } - virtual uint32_t GetIncomingFrameRate( - const uint32_t stream_id) { + virtual uint32_t GetIncomingFrameRate(const uint32_t stream_id) OVERRIDE { return 0; } - virtual uint32_t GetNumIncomingRenderStreams() const { + virtual uint32_t GetNumIncomingRenderStreams() const OVERRIDE { return static_cast<uint32_t>(stream_render_map_.size()); } - virtual bool HasIncomingRenderStream(const uint32_t stream_id) const; + virtual bool HasIncomingRenderStream(const uint32_t stream_id) const OVERRIDE; virtual int32_t RegisterRawFrameCallback( const uint32_t stream_id, - webrtc::VideoRenderCallback* callback_obj) { + webrtc::VideoRenderCallback* callback_obj) OVERRIDE { return -1; } virtual int32_t GetLastRenderedFrame( const uint32_t stream_id, - webrtc::I420VideoFrame &frame) const { + webrtc::I420VideoFrame &frame) const OVERRIDE { return -1; } - virtual int32_t StartRender( - const uint32_t stream_id); + virtual int32_t StartRender(const uint32_t stream_id) OVERRIDE; - virtual int32_t StopRender( - const uint32_t stream_id); + virtual int32_t StopRender(const uint32_t stream_id) OVERRIDE; - virtual int32_t ResetRender() { return 0; } + virtual int32_t ResetRender() OVERRIDE { return 0; } - virtual webrtc::RawVideoType PreferredVideoType() const; + virtual webrtc::RawVideoType PreferredVideoType() const OVERRIDE; - virtual bool IsFullScreen() { return false; } + virtual bool IsFullScreen() OVERRIDE { return false; } virtual int32_t GetScreenResolution(uint32_t& screenWidth, - uint32_t& screenHeight) const { + uint32_t& screenHeight) const OVERRIDE { return -1; } - virtual uint32_t RenderFrameRate( - const uint32_t stream_id) { + virtual uint32_t RenderFrameRate(const uint32_t stream_id) OVERRIDE { return 0; } @@ -135,11 +125,12 @@ class WebRtcPassthroughRender : public webrtc::VideoRender { const uint32_t stream_id, const float left, const float top, const float right, - const float bottom) { + const float bottom) OVERRIDE { return -1; } - virtual int32_t SetExpectedRenderDelay(uint32_t stream_id, int32_t delay_ms) { + virtual int32_t SetExpectedRenderDelay(uint32_t stream_id, + int32_t delay_ms) OVERRIDE { return -1; } @@ -148,22 +139,22 @@ class WebRtcPassthroughRender : public webrtc::VideoRender { const unsigned int zOrder, const float left, const float top, const float right, - const float bottom) { + const float bottom) OVERRIDE { return -1; } - virtual int32_t SetTransparentBackground(const bool enable) { + virtual int32_t SetTransparentBackground(const bool enable) OVERRIDE { return -1; } - virtual int32_t FullScreenRender(void* window, const bool enable) { + virtual int32_t FullScreenRender(void* window, const bool enable) OVERRIDE { return -1; } virtual int32_t SetBitmap(const void* bitMap, const uint8_t pictureId, const void* colorKey, const float left, const float top, - const float right, const float bottom) { + const float right, const float bottom) OVERRIDE { return -1; } @@ -173,27 +164,27 @@ class WebRtcPassthroughRender : public webrtc::VideoRender { const uint32_t textColorRef, const uint32_t backgroundColorRef, const float left, const float top, - const float right, const float bottom) { + const float right, const float bottom) OVERRIDE { return -1; } virtual int32_t SetStartImage( const uint32_t stream_id, - const webrtc::I420VideoFrame& videoFrame) { + const webrtc::I420VideoFrame& videoFrame) OVERRIDE { return -1; } virtual int32_t SetTimeoutImage( const uint32_t stream_id, const webrtc::I420VideoFrame& videoFrame, - const uint32_t timeout) { + const uint32_t timeout) OVERRIDE { return -1; } virtual int32_t MirrorRenderStream(const int renderId, const bool enable, const bool mirrorXAxis, - const bool mirrorYAxis) { + const bool mirrorYAxis) OVERRIDE { return -1; } diff --git a/media/webrtc/webrtcvideoengine.cc b/media/webrtc/webrtcvideoengine.cc index 83b1177..b2533b3 100644 --- a/media/webrtc/webrtcvideoengine.cc +++ b/media/webrtc/webrtcvideoengine.cc @@ -64,6 +64,22 @@ #include "webrtc/experiments.h" #include "webrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h" +namespace { + +template <class T> +bool Changed(cricket::Settable<T> proposed, + cricket::Settable<T> original) { + return proposed.IsSet() && proposed != original; +} + +template <class T> +bool Changed(cricket::Settable<T> proposed, + cricket::Settable<T> original, + T* value) { + return proposed.Get(value) && proposed != original; +} + +} // namespace namespace cricket { @@ -81,6 +97,9 @@ const int kMaxVideoBitrate = 2000; const int kCpuMonitorPeriodMs = 2000; // 2 seconds. +// TODO(pthatcher): Figure out what the proper value here is, or if we +// can just remove this altogether. +static const int kDefaultRenderDelayMs = 100; static const int kDefaultLogSeverity = rtc::LS_WARNING; @@ -926,8 +945,6 @@ void WebRtcVideoEngine::Construct(ViEWrapper* vie_wrapper, initialized_ = false; SetTraceFilter(SeverityToFilter(kDefaultLogSeverity)); render_module_.reset(new WebRtcPassthroughRender()); - local_renderer_w_ = local_renderer_h_ = 0; - local_renderer_ = NULL; capture_started_ = false; decoder_factory_ = NULL; encoder_factory_ = NULL; @@ -953,6 +970,9 @@ void WebRtcVideoEngine::Construct(ViEWrapper* vie_wrapper, LOG(LS_ERROR) << "Failed to initialize list of supported codec types"; } + // Consider jitter, packet loss, etc when rendering. This will + // theoretically make rendering more smooth. + EnableTimedRender(); // Load our RTP Header extensions. rtp_header_extensions_.push_back( @@ -1059,10 +1079,6 @@ int WebRtcVideoEngine::GetCapabilities() { return VIDEO_RECV | VIDEO_SEND; } -bool WebRtcVideoEngine::SetOptions(const VideoOptions &options) { - return true; -} - bool WebRtcVideoEngine::SetDefaultEncoderConfig( const VideoEncoderConfig& config) { return SetDefaultCodec(config.max_codec); @@ -1109,12 +1125,6 @@ WebRtcVideoMediaChannel* WebRtcVideoEngine::CreateChannel( return channel; } -bool WebRtcVideoEngine::SetLocalRenderer(VideoRenderer* renderer) { - local_renderer_w_ = local_renderer_h_ = 0; - local_renderer_ = renderer; - return true; -} - const std::vector<VideoCodec>& WebRtcVideoEngine::codecs() const { return video_codecs_; } @@ -1568,6 +1578,7 @@ WebRtcVideoMediaChannel::WebRtcVideoMediaChannel( remb_enabled_(false), render_started_(false), first_receive_ssrc_(kSsrcUnset), + receiver_report_ssrc_(kSsrcUnset), num_unsignalled_recv_channels_(0), send_rtx_type_(-1), send_red_type_(-1), @@ -1580,7 +1591,19 @@ WebRtcVideoMediaChannel::WebRtcVideoMediaChannel( bool WebRtcVideoMediaChannel::Init() { const uint32 ssrc_key = 0; - return CreateChannel(ssrc_key, MD_SENDRECV, &default_channel_id_); + bool result = CreateChannel(ssrc_key, MD_SENDRECV, &default_channel_id_); + if (!result) { + return false; + } + if (voice_channel_) { + WebRtcVoiceMediaChannel* voice_channel = + static_cast<WebRtcVoiceMediaChannel*>(voice_channel_); + if (!voice_channel->SetupSharedBandwidthEstimation( + engine()->vie()->engine(), default_channel_id_)) { + return false; + } + } + return true; } WebRtcVideoMediaChannel::~WebRtcVideoMediaChannel() { @@ -1752,6 +1775,35 @@ bool WebRtcVideoMediaChannel::SetSendCodecs( return true; } +bool WebRtcVideoMediaChannel::MaybeRegisterExternalEncoder( + WebRtcVideoChannelSendInfo* send_channel, + const webrtc::VideoCodec& codec) { + // Codec type not supported or encoder already registered, so + // nothing to do. + if (!engine()->IsExternalEncoderCodecType(codec.codecType) + || send_channel->IsEncoderRegistered(codec.plType)) { + return true; + } + + webrtc::VideoEncoder* encoder = + engine()->CreateExternalEncoder(codec.codecType); + if (!encoder) { + // No encoder factor, so nothing to do. + return true; + } + + const int channel_id = send_channel->channel_id(); + if (engine()->vie()->ext_codec()->RegisterExternalSendCodec( + channel_id, codec.plType, encoder, false) != 0) { + LOG_RTCERR2(RegisterExternalSendCodec, channel_id, codec.plName); + engine()->DestroyExternalEncoder(encoder); + return false; + } + + send_channel->RegisterEncoder(codec.plType, encoder); + return true; +} + bool WebRtcVideoMediaChannel::GetSendCodec(VideoCodec* send_codec) { if (!send_codec_) { return false; @@ -1861,18 +1913,9 @@ bool WebRtcVideoMediaChannel::AddSendStream(const StreamParams& sp) { } WebRtcVideoChannelSendInfo* send_channel = GetSendChannelBySsrcKey(ssrc_key); - // Set the send (local) SSRC. // If there are multiple send SSRCs, we can only set the first one here, and // the rest of the SSRC(s) need to be set after SetSendCodec has been called - // (with a codec requires multiple SSRC(s)). - if (engine()->vie()->rtp()->SetLocalSSRC(channel_id, - sp.first_ssrc()) != 0) { - LOG_RTCERR2(SetLocalSSRC, channel_id, sp.first_ssrc()); - return false; - } - - // Set the corresponding RTX SSRC. - if (!SetLocalRtxSsrc(channel_id, sp, sp.first_ssrc(), 0)) { + if (!SetLimitedNumberOfSendSsrcs(channel_id, sp, 1)) { return false; } @@ -1883,21 +1926,9 @@ bool WebRtcVideoMediaChannel::AddSendStream(const StreamParams& sp) { return false; } - // At this point the channel's local SSRC has been updated. If the channel is - // the default channel make sure that all the receive channels are updated as - // well. Receive channels have to have the same SSRC as the default channel in - // order to send receiver reports with this SSRC. + // Use the SSRC of the default channel in the RTCP receiver reports. if (IsDefaultChannelId(channel_id)) { - for (RecvChannelMap::const_iterator it = recv_channels_.begin(); - it != recv_channels_.end(); ++it) { - WebRtcVideoChannelRecvInfo* info = it->second; - int channel_id = info->channel_id(); - if (engine()->vie()->rtp()->SetLocalSSRC(channel_id, - sp.first_ssrc()) != 0) { - LOG_RTCERR1(SetLocalSSRC, it->first); - return false; - } - } + SetReceiverReportSsrc(sp.first_ssrc()); } send_channel->set_stream_params(sp); @@ -2018,25 +2049,6 @@ bool WebRtcVideoMediaChannel::AddRecvStream(const StreamParams& sp) { return false; } - // Get the default renderer. - VideoRenderer* default_renderer = NULL; - if (InConferenceMode()) { - // The recv_channels_ size start out being 1, so if it is two here - // this is the first receive channel created (default_channel_id_ - // is not used for receiving in a conference call). This means - // that the renderer stored inside default_channel_id_ should be - // used for the just created channel. - if (recv_channels_.size() == 2 && GetDefaultRecvChannel()) { - GetDefaultRenderer(&default_renderer); - } - } - - // The first recv stream reuses the default renderer (if a default renderer - // has been set). - if (default_renderer) { - SetRenderer(sp.first_ssrc(), default_renderer); - } - LOG(LS_INFO) << "New video stream " << sp.first_ssrc() << " registered to VideoEngine channel #" << channel_id << " and connected to channel #" @@ -2228,6 +2240,9 @@ bool WebRtcVideoMediaChannel::GetSendChannelSsrcKey(uint32 local_ssrc, return true; } if (!GetSendChannelBySsrcKey(local_ssrc)) { + // If a stream has multiple ssrcs, the local_ssrc could be any of + // them, but we use the first one (StreamParams::first_ssrc()) as + // the key. for (SendChannelMap::iterator iter = send_channels_.begin(); iter != send_channels_.end(); ++iter) { WebRtcVideoChannelSendInfo* send_channel = iter->second; @@ -2898,7 +2913,7 @@ bool WebRtcVideoMediaChannel::SetStartSendBandwidth(int bps) { } // On success, SetSendCodec() will reset |send_start_bitrate_| to |bps/1000|, - // by calling MaybeChangeBitrates. That method will also clamp the + // by calling SanitizeBitrates. That method will also clamp the // start bitrate between min and max, consistent with the override behavior // in SetMaxSendBandwidth. webrtc::VideoCodec new_codec = *send_codec_; @@ -2934,39 +2949,10 @@ bool WebRtcVideoMediaChannel::SetOptions(const VideoOptions &options) { return true; } - // Trigger SetSendCodec to set correct noise reduction state if the option has - // changed. - bool denoiser_changed = options.video_noise_reduction.IsSet() && - (options_.video_noise_reduction != options.video_noise_reduction); - - bool leaky_bucket_changed = options.video_leaky_bucket.IsSet() && - (options_.video_leaky_bucket != options.video_leaky_bucket); - - bool buffer_latency_changed = options.buffered_mode_latency.IsSet() && - (options_.buffered_mode_latency != options.buffered_mode_latency); - - bool dscp_option_changed = (options_.dscp != options.dscp); - - bool suspend_below_min_bitrate_changed = - options.suspend_below_min_bitrate.IsSet() && - (options_.suspend_below_min_bitrate != options.suspend_below_min_bitrate); - - bool conference_mode_turned_off = false; - if (options_.conference_mode.IsSet() && options.conference_mode.IsSet() && - options_.conference_mode.GetWithDefaultIfUnset(false) && - !options.conference_mode.GetWithDefaultIfUnset(false)) { - conference_mode_turned_off = true; - } - -#ifdef USE_WEBRTC_DEV_BRANCH - bool payload_padding_changed = options.use_payload_padding.IsSet() && - options_.use_payload_padding != options.use_payload_padding; -#endif - - // Save the options, to be interpreted where appropriate. // Use options_.SetAll() instead of assignment so that unset value in options // will not overwrite the previous option value. + VideoOptions original = options_; options_.SetAll(options); // Set CPU options for all send channels. @@ -2977,38 +2963,36 @@ bool WebRtcVideoMediaChannel::SetOptions(const VideoOptions &options) { } if (send_codec_) { - bool reset_send_codec_needed = denoiser_changed; webrtc::VideoCodec new_codec = *send_codec_; + bool conference_mode_turned_off = ( + original.conference_mode.IsSet() && + options.conference_mode.IsSet() && + original.conference_mode.GetWithDefaultIfUnset(false) && + !options.conference_mode.GetWithDefaultIfUnset(false)); if (conference_mode_turned_off) { // This is a special case for turning conference mode off. // Max bitrate should go back to the default maximum value instead // of the current maximum. new_codec.maxBitrate = kAutoBandwidth; - reset_send_codec_needed = true; } // TODO(pthatcher): Remove this. We don't need 4 ways to set bitrates. int new_start_bitrate; if (options.video_start_bitrate.Get(&new_start_bitrate)) { new_codec.startBitrate = new_start_bitrate; - reset_send_codec_needed = true; } - - LOG(LS_INFO) << "Reset send codec needed is enabled? " - << reset_send_codec_needed; - if (reset_send_codec_needed) { - if (!SetSendCodec(new_codec)) { - return false; - } - LogSendCodecChange("SetOptions()"); + if (!SetSendCodec(new_codec)) { + return false; } + LogSendCodecChange("SetOptions()"); } - if (leaky_bucket_changed) { - bool enable_leaky_bucket = - options_.video_leaky_bucket.GetWithDefaultIfUnset(true); + bool enable_leaky_bucket; + if (Changed(options.video_leaky_bucket, + original.video_leaky_bucket, + &enable_leaky_bucket)) { LOG(LS_INFO) << "Leaky bucket is enabled? " << enable_leaky_bucket; for (SendChannelMap::iterator it = send_channels_.begin(); it != send_channels_.end(); ++it) { @@ -3022,10 +3006,11 @@ bool WebRtcVideoMediaChannel::SetOptions(const VideoOptions &options) { } } } - if (buffer_latency_changed) { - int buffer_latency = - options_.buffered_mode_latency.GetWithDefaultIfUnset( - cricket::kBufferedModeDisabled); + + int buffer_latency; + if (Changed(options.buffered_mode_latency, + original.buffered_mode_latency, + &buffer_latency)) { LOG(LS_INFO) << "Buffer latency is " << buffer_latency; for (SendChannelMap::iterator it = send_channels_.begin(); it != send_channels_.end(); ++it) { @@ -3044,17 +3029,24 @@ bool WebRtcVideoMediaChannel::SetOptions(const VideoOptions &options) { } } } - if (dscp_option_changed) { + + bool dscp_enabled; + if (Changed(options.dscp, original.dscp, &dscp_enabled)) { rtc::DiffServCodePoint dscp = rtc::DSCP_DEFAULT; - if (options_.dscp.GetWithDefaultIfUnset(false)) + if (dscp_enabled) { dscp = kVideoDscpValue; + } LOG(LS_INFO) << "DSCP is " << dscp; if (MediaChannel::SetDscp(dscp) != 0) { LOG(LS_WARNING) << "Failed to set DSCP settings for video channel"; } } - if (suspend_below_min_bitrate_changed) { - if (options_.suspend_below_min_bitrate.GetWithDefaultIfUnset(false)) { + + bool suspend_below_min_bitrate; + if (Changed(options.suspend_below_min_bitrate, + original.suspend_below_min_bitrate, + &suspend_below_min_bitrate)) { + if (suspend_below_min_bitrate) { LOG(LS_INFO) << "Suspend below min bitrate enabled."; for (SendChannelMap::iterator it = send_channels_.begin(); it != send_channels_.end(); ++it) { @@ -3065,14 +3057,17 @@ bool WebRtcVideoMediaChannel::SetOptions(const VideoOptions &options) { LOG(LS_WARNING) << "Cannot disable video suspension once it is enabled"; } } + #ifdef USE_WEBRTC_DEV_BRANCH - if (payload_padding_changed) { + bool use_payload_padding; + if (Changed(options.use_payload_padding, + original.use_payload_padding, + &use_payload_padding)) { LOG(LS_INFO) << "Payload-based padding called."; for (SendChannelMap::iterator it = send_channels_.begin(); it != send_channels_.end(); ++it) { engine()->vie()->rtp()->SetPadWithRedundantPayloads( - it->second->channel_id(), - options_.use_payload_padding.GetWithDefaultIfUnset(false)); + it->second->channel_id(), use_payload_padding); } } #endif @@ -3139,10 +3134,6 @@ bool WebRtcVideoMediaChannel::GetRenderer(uint32 ssrc, return true; } -bool WebRtcVideoMediaChannel::GetDefaultRenderer(VideoRenderer** renderer) { - return GetRenderer(kDefaultChannelSsrcKey, renderer); -} - bool WebRtcVideoMediaChannel::GetVideoAdapter( uint32 ssrc, CoordinatedVideoAdapter** video_adapter) { WebRtcVideoChannelSendInfo* send_channel = GetSendChannelBySsrc(ssrc); @@ -3413,6 +3404,11 @@ bool WebRtcVideoMediaChannel::ConfigureReceiving(int channel_id, return false; } + if (engine()->vie()->render()->SetExpectedRenderDelay( + channel_id, kDefaultRenderDelayMs)) { + LOG_RTCERR2(SetExpectedRenderDelay, + channel_id, kDefaultRenderDelayMs); + } if (engine_->vie()->rtp()->SetRembStatus(channel_id, kNotSending, @@ -3431,20 +3427,13 @@ bool WebRtcVideoMediaChannel::ConfigureReceiving(int channel_id, return false; } - if (remote_ssrc != kDefaultChannelSsrcKey) { - // Use the same SSRC as our default channel - // (so the RTCP reports are correct). - unsigned int send_ssrc = 0; - webrtc::ViERTP_RTCP* rtp = engine()->vie()->rtp(); - if (rtp->GetLocalSSRC(default_channel_id_, send_ssrc) == -1) { - LOG_RTCERR2(GetLocalSSRC, default_channel_id_, send_ssrc); - return false; - } - if (rtp->SetLocalSSRC(channel_id, send_ssrc) == -1) { - LOG_RTCERR2(SetLocalSSRC, channel_id, send_ssrc); + if (receiver_report_ssrc_ != kSsrcUnset) { + if (engine()->vie()->rtp()->SetLocalSSRC( + channel_id, receiver_report_ssrc_) == -1) { + LOG_RTCERR2(SetLocalSSRC, channel_id, receiver_report_ssrc_); return false; } - } // Else this is the the default channel and we don't change the SSRC. + } // Disable color enhancement since it is a bit too aggressive. if (engine()->vie()->image()->EnableColorEnhancement(channel_id, @@ -3673,21 +3662,7 @@ bool WebRtcVideoMediaChannel::SetSendCodec( target_codec.codecSpecific.VP8.denoisingOn = enable_denoising; } - // Register external encoder if codec type is supported by encoder factory. - if (engine()->IsExternalEncoderCodecType(codec.codecType) && - !send_channel->IsEncoderRegistered(target_codec.plType)) { - webrtc::VideoEncoder* encoder = - engine()->CreateExternalEncoder(codec.codecType); - if (encoder) { - if (engine()->vie()->ext_codec()->RegisterExternalSendCodec( - channel_id, target_codec.plType, encoder, false) == 0) { - send_channel->RegisterEncoder(target_codec.plType, encoder); - } else { - LOG_RTCERR2(RegisterExternalSendCodec, channel_id, target_codec.plName); - engine()->DestroyExternalEncoder(encoder); - } - } - } + MaybeRegisterExternalEncoder(send_channel, target_codec); // Resolution and framerate may vary for different send channels. const VideoFormat& video_format = send_channel->video_format(); @@ -3698,7 +3673,8 @@ bool WebRtcVideoMediaChannel::SetSendCodec( LOG(LS_INFO) << "0x0 resolution selected. Captured frames will be dropped " << "for ssrc: " << ssrc << "."; } else { - MaybeChangeBitrates(channel_id, &target_codec); + StreamParams* send_params = send_channel->stream_params(); + SanitizeBitrates(channel_id, &target_codec); webrtc::VideoCodec current_codec; if (!engine()->vie()->codec()->GetSendCodec(channel_id, current_codec)) { // Compare against existing configured send codec. @@ -3713,6 +3689,11 @@ bool WebRtcVideoMediaChannel::SetSendCodec( return false; } + if (send_params) { + if (!SetSendSsrcs(channel_id, *send_params, target_codec)) { + return false; + } + } // NOTE: SetRtxSendPayloadType must be called after all simulcast SSRCs // are configured. Otherwise ssrc's configured after this point will use // the primary PT for RTX. @@ -3956,6 +3937,7 @@ bool WebRtcVideoMediaChannel::MaybeResetVieSendCodec( int screencast_min_bitrate = options_.screencast_min_bitrate.GetWithDefaultIfUnset(0); bool leaky_bucket = options_.video_leaky_bucket.GetWithDefaultIfUnset(true); + StreamParams* send_params = send_channel->stream_params(); bool reset_send_codec = target_width != cur_width || target_height != cur_height; if (vie_codec.codecType == webrtc::kVideoCodecVP8) { @@ -3979,7 +3961,7 @@ bool WebRtcVideoMediaChannel::MaybeResetVieSendCodec( vie_codec.codecSpecific.VP8.denoisingOn = enable_denoising; vie_codec.codecSpecific.VP8.frameDroppingOn = vp8_frame_dropping; } - MaybeChangeBitrates(channel_id, &vie_codec); + SanitizeBitrates(channel_id, &vie_codec); if (engine()->vie()->codec()->SetSendCodec(channel_id, vie_codec) != 0) { LOG_RTCERR1(SetSendCodec, channel_id); @@ -4001,6 +3983,13 @@ bool WebRtcVideoMediaChannel::MaybeResetVieSendCodec( engine()->vie()->rtp()->SetTransmissionSmoothingStatus(channel_id, leaky_bucket); } + // TODO(sriniv): SetSendCodec already sets ssrc's like below. + // Consider removing. + if (send_params) { + if (!SetSendSsrcs(channel_id, *send_params, target_codec)) { + return false; + } + } if (reset) { *reset = true; } @@ -4010,7 +3999,7 @@ bool WebRtcVideoMediaChannel::MaybeResetVieSendCodec( return true; } -void WebRtcVideoMediaChannel::MaybeChangeBitrates( +void WebRtcVideoMediaChannel::SanitizeBitrates( int channel_id, webrtc::VideoCodec* codec) { codec->minBitrate = GetBitrate(codec->minBitrate, kMinVideoBitrate); codec->startBitrate = GetBitrate(codec->startBitrate, kStartVideoBitrate); @@ -4047,7 +4036,6 @@ void WebRtcVideoMediaChannel::MaybeChangeBitrates( codec->startBitrate = current_target_bitrate; } } - } void WebRtcVideoMediaChannel::OnMessage(rtc::Message* msg) { @@ -4156,21 +4144,55 @@ bool WebRtcVideoMediaChannel::SetHeaderExtension(ExtensionSetterFunction setter, return SetHeaderExtension(setter, channel_id, extension); } -bool WebRtcVideoMediaChannel::SetLocalRtxSsrc(int channel_id, - const StreamParams& send_params, - uint32 primary_ssrc, - int stream_idx) { - uint32 rtx_ssrc = 0; - bool has_rtx = send_params.GetFidSsrc(primary_ssrc, &rtx_ssrc); - if (has_rtx && engine()->vie()->rtp()->SetLocalSSRC( - channel_id, rtx_ssrc, webrtc::kViEStreamTypeRtx, stream_idx) != 0) { - LOG_RTCERR4(SetLocalSSRC, channel_id, rtx_ssrc, - webrtc::kViEStreamTypeRtx, stream_idx); +bool WebRtcVideoMediaChannel::SetPrimaryAndRtxSsrcs( + int channel_id, int idx, uint32 primary_ssrc, + const StreamParams& send_params) { + LOG(LS_INFO) << "Set primary ssrc " << primary_ssrc + << " on channel " << channel_id << " idx " << idx; + if (engine()->vie()->rtp()->SetLocalSSRC( + channel_id, primary_ssrc, webrtc::kViEStreamTypeNormal, idx) != 0) { + LOG_RTCERR4(SetLocalSSRC, + channel_id, primary_ssrc, webrtc::kViEStreamTypeNormal, idx); return false; } + + uint32 rtx_ssrc = 0; + if (send_params.GetFidSsrc(primary_ssrc, &rtx_ssrc)) { + LOG(LS_INFO) << "Set rtx ssrc " << rtx_ssrc + << " on channel " << channel_id << " idx " << idx; + if (engine()->vie()->rtp()->SetLocalSSRC( + channel_id, rtx_ssrc, webrtc::kViEStreamTypeRtx, idx) != 0) { + LOG_RTCERR4(SetLocalSSRC, + channel_id, rtx_ssrc, webrtc::kViEStreamTypeRtx, idx); + return false; + } + } + return true; +} + +bool WebRtcVideoMediaChannel::SetLimitedNumberOfSendSsrcs( + int channel_id, const StreamParams& sp, size_t limit) { + const SsrcGroup* sim_group = sp.get_ssrc_group(kSimSsrcGroupSemantics); + if (!sim_group || limit == 1) { + return SetPrimaryAndRtxSsrcs(channel_id, 0, sp.first_ssrc(), sp); + } + + std::vector<uint32> ssrcs = sim_group->ssrcs; + for (size_t i = 0; i < ssrcs.size() && i < limit; ++i) { + if (!SetPrimaryAndRtxSsrcs(channel_id, static_cast<int>(i), ssrcs[i], sp)) { + return false; + } + } return true; } +bool WebRtcVideoMediaChannel::SetSendSsrcs( + int channel_id, const StreamParams& sp, + const webrtc::VideoCodec& codec) { + // TODO(pthatcher): Support more than one primary SSRC per stream. + return SetLimitedNumberOfSendSsrcs(channel_id, sp, 1); +} + void WebRtcVideoMediaChannel::MaybeConnectCapturer(VideoCapturer* capturer) { if (capturer && GetSendChannelNum(capturer) == 1) { capturer->SignalVideoFrame.connect(this, @@ -4184,6 +4206,18 @@ void WebRtcVideoMediaChannel::MaybeDisconnectCapturer(VideoCapturer* capturer) { } } +void WebRtcVideoMediaChannel::SetReceiverReportSsrc(uint32 ssrc) { + for (RecvChannelMap::const_iterator it = recv_channels_.begin(); + it != recv_channels_.end(); ++it) { + int channel_id = it->second->channel_id(); + if (engine()->vie()->rtp()->SetLocalSSRC(channel_id, ssrc) != 0) { + LOG_RTCERR2(SetLocalSSRC, channel_id, ssrc); + ASSERT(false); + } + } + receiver_report_ssrc_ = ssrc; +} + } // namespace cricket #endif // HAVE_WEBRTC_VIDEO diff --git a/media/webrtc/webrtcvideoengine.h b/media/webrtc/webrtcvideoengine.h index c31f547..6f939d2 100644 --- a/media/webrtc/webrtcvideoengine.h +++ b/media/webrtc/webrtcvideoengine.h @@ -42,6 +42,9 @@ #if !defined(LIBPEERCONNECTION_LIB) && \ !defined(LIBPEERCONNECTION_IMPLEMENTATION) +// If you hit this, then you've tried to include this header from outside +// a shared library. An instance of this class must only be created from +// within the library that actually implements it. #error "Bogus include." #endif @@ -98,24 +101,23 @@ class WebRtcVideoEngine : public sigslot::has_slots<>, ViEWrapper* vie_wrapper, ViETraceWrapper* tracing, rtc::CpuMonitor* cpu_monitor); - ~WebRtcVideoEngine(); + virtual ~WebRtcVideoEngine(); // Basic video engine implementation. bool Init(rtc::Thread* worker_thread); void Terminate(); int GetCapabilities(); - bool SetOptions(const VideoOptions &options); bool SetDefaultEncoderConfig(const VideoEncoderConfig& config); VideoEncoderConfig GetDefaultEncoderConfig() const; - WebRtcVideoMediaChannel* CreateChannel(VoiceMediaChannel* voice_channel); + virtual WebRtcVideoMediaChannel* CreateChannel( + VoiceMediaChannel* voice_channel); const std::vector<VideoCodec>& codecs() const; const std::vector<RtpHeaderExtension>& rtp_header_extensions() const; void SetLogging(int min_sev, const char* filter); - bool SetLocalRenderer(VideoRenderer* renderer); sigslot::repeater2<VideoCapturer*, CaptureState> SignalCaptureStateChange; // Set the VoiceEngine for A/V sync. This can only be called before Init. @@ -127,7 +129,8 @@ class WebRtcVideoEngine : public sigslot::has_slots<>, // Set a WebRtcVideoEncoderFactory for external encoding. Video engine does // not take the ownership of |encoder_factory|. The caller needs to make sure // that |encoder_factory| outlives the video engine. - void SetExternalEncoderFactory(WebRtcVideoEncoderFactory* encoder_factory); + virtual void SetExternalEncoderFactory( + WebRtcVideoEncoderFactory* encoder_factory); // Enable the render module with timing control. bool EnableTimedRender(); @@ -225,9 +228,6 @@ class WebRtcVideoEngine : public sigslot::has_slots<>, VideoChannels channels_; bool capture_started_; - int local_renderer_w_; - int local_renderer_h_; - VideoRenderer* local_renderer_; rtc::scoped_ptr<rtc::CpuMonitor> cpu_monitor_; }; @@ -238,7 +238,7 @@ class WebRtcVideoMediaChannel : public rtc::MessageHandler, public: WebRtcVideoMediaChannel(WebRtcVideoEngine* engine, VoiceMediaChannel* voice_channel); - ~WebRtcVideoMediaChannel(); + virtual ~WebRtcVideoMediaChannel(); bool Init(); WebRtcVideoEngine* engine() { return engine_; } @@ -310,6 +310,20 @@ class WebRtcVideoMediaChannel : public rtc::MessageHandler, virtual int SendPacket(int channel, const void* data, int len); virtual int SendRTCPPacket(int channel, const void* data, int len); + // Checks the current bitrate estimate and modifies the bitrates + // accordingly, including converting kAutoBandwidth to the correct defaults. + virtual void SanitizeBitrates( + int channel_id, webrtc::VideoCodec* video_codec); + virtual void LogSendCodecChange(const std::string& reason); + bool SetPrimaryAndRtxSsrcs( + int channel_id, int idx, uint32 primary_ssrc, + const StreamParams& send_params); + bool SetLimitedNumberOfSendSsrcs( + int channel_id, const StreamParams& send_params, size_t limit); + virtual bool SetSendSsrcs( + int channel_id, const StreamParams& send_params, + const webrtc::VideoCodec& codec); + private: typedef std::map<uint32, WebRtcVideoChannelRecvInfo*> RecvChannelMap; typedef std::map<uint32, WebRtcVideoChannelSendInfo*> SendChannelMap; @@ -339,22 +353,23 @@ class WebRtcVideoMediaChannel : public rtc::MessageHandler, bool SetSendCodec(const webrtc::VideoCodec& codec); bool SetSendCodec(WebRtcVideoChannelSendInfo* send_channel, const webrtc::VideoCodec& codec); - void LogSendCodecChange(const std::string& reason); // Prepares the channel with channel id |info->channel_id()| to receive all // codecs in |receive_codecs_| and start receive packets. bool SetReceiveCodecs(WebRtcVideoChannelRecvInfo* info); // Returns the channel ID that receives the stream with SSRC |ssrc|. int GetRecvChannelId(uint32 ssrc); bool MaybeSetRtxSsrc(const StreamParams& sp, int channel_id); + // Create and register an external endcoder if it's possible to do + // so and one isn't already registered. + bool MaybeRegisterExternalEncoder( + WebRtcVideoChannelSendInfo* send_channel, + const webrtc::VideoCodec& codec); // Given captured video frame size, checks if we need to reset vie send codec. // |reset| is set to whether resetting has happened on vie or not. // Returns false on error. bool MaybeResetVieSendCodec(WebRtcVideoChannelSendInfo* send_channel, int new_width, int new_height, bool is_screencast, bool* reset); - // Checks the current bitrate estimate and modifies the bitrates - // accordingly, including converting kAutoBandwidth to the correct defaults. - void MaybeChangeBitrates(int channel_id, webrtc::VideoCodec* video_codec); // Helper function for starting the sending of media on all channels or // |channel_id|. Note that these two function do not change |sending_|. bool StartSend(); @@ -371,9 +386,10 @@ class WebRtcVideoMediaChannel : public rtc::MessageHandler, // Returns the ssrc key corresponding to the provided local SSRC in // |ssrc_key|. The return value is true upon success. If the local // ssrc correspond to that of the default channel the key is - // kDefaultChannelSsrcKey. - // For all other channels the returned ssrc key will be the same as - // the local ssrc. + // kDefaultChannelSsrcKey. For all other channels the returned ssrc + // key will be the same as the local ssrc. If a stream has more + // than one ssrc, the first (corresponding to + // StreamParams::first_ssrc()) is used as the key. bool GetSendChannelSsrcKey(uint32 local_ssrc, uint32* ssrc_key); WebRtcVideoChannelSendInfo* GetDefaultSendChannel(); WebRtcVideoChannelSendInfo* GetSendChannelBySsrcKey(uint32 ssrc_key); @@ -387,8 +403,6 @@ class WebRtcVideoMediaChannel : public rtc::MessageHandler, bool IsDefaultChannelId(int channel_id) const { return channel_id == default_channel_id_; } - bool GetDefaultRenderer(VideoRenderer** renderer); - bool DeleteSendChannel(uint32 ssrc_key); WebRtcVideoChannelRecvInfo* GetDefaultRecvChannel(); @@ -414,10 +428,6 @@ class WebRtcVideoMediaChannel : public rtc::MessageHandler, // Signal when cpu adaptation has no further scope to adapt. void OnCpuAdaptationUnable(); - // Set the local (send-side) RTX SSRC corresponding to primary_ssrc. - bool SetLocalRtxSsrc(int channel_id, const StreamParams& send_params, - uint32 primary_ssrc, int stream_idx); - // Connect |capturer| to WebRtcVideoMediaChannel if it is only registered // to one send channel, i.e. the first send channel. void MaybeConnectCapturer(VideoCapturer* capturer); @@ -427,6 +437,9 @@ class WebRtcVideoMediaChannel : public rtc::MessageHandler, bool RemoveRecvStreamInternal(uint32 ssrc); + // Set the ssrc to use for RTCP receiver reports. + void SetReceiverReportSsrc(uint32 ssrc); + // Global state. WebRtcVideoEngine* engine_; VoiceMediaChannel* voice_channel_; @@ -454,6 +467,7 @@ class WebRtcVideoMediaChannel : public rtc::MessageHandler, std::map<int, int> associated_payload_types_; bool render_started_; uint32 first_receive_ssrc_; + uint32 receiver_report_ssrc_; std::vector<RtpHeaderExtension> receive_extensions_; int num_unsignalled_recv_channels_; diff --git a/media/webrtc/webrtcvideoengine2.cc b/media/webrtc/webrtcvideoengine2.cc index ea53596..26e3079 100644 --- a/media/webrtc/webrtcvideoengine2.cc +++ b/media/webrtc/webrtcvideoengine2.cc @@ -42,8 +42,7 @@ #include "webrtc/base/logging.h" #include "webrtc/base/stringutils.h" #include "webrtc/call.h" -// TODO(pbos): Move codecs out of modules (webrtc:3070). -#include "webrtc/modules/video_coding/codecs/vp8/include/vp8.h" +#include "webrtc/video_encoder.h" #define UNIMPLEMENTED \ LOG(LS_ERROR) << "Call to unimplemented function " << __FUNCTION__; \ @@ -55,6 +54,8 @@ namespace cricket { // duration hasn't been implemented. static const int kNackHistoryMs = 1000; +static const int kDefaultQpMax = 56; + static const int kDefaultRtcpReceiverReportSsrc = 1; struct VideoCodecPref { @@ -187,7 +188,7 @@ std::vector<webrtc::VideoStream> WebRtcVideoEncoderFactory2::CreateVideoStreams( stream.min_bitrate_bps = min_bitrate * 1000; stream.target_bitrate_bps = stream.max_bitrate_bps = max_bitrate * 1000; - int max_qp = 56; + int max_qp = kDefaultQpMax; codec.GetParam(kCodecParamMaxQuantization, &max_qp); stream.max_qp = max_qp; std::vector<webrtc::VideoStream> streams; @@ -200,7 +201,7 @@ webrtc::VideoEncoder* WebRtcVideoEncoderFactory2::CreateVideoEncoder( const VideoOptions& options) { assert(SupportsCodec(codec)); if (_stricmp(codec.name.c_str(), kVp8CodecName) == 0) { - return webrtc::VP8Encoder::Create(); + return webrtc::VideoEncoder::Create(webrtc::VideoEncoder::kVp8); } // This shouldn't happen, we should be able to create encoders for all codecs // we support. @@ -213,14 +214,9 @@ void* WebRtcVideoEncoderFactory2::CreateVideoEncoderSettings( const VideoOptions& options) { assert(SupportsCodec(codec)); if (_stricmp(codec.name.c_str(), kVp8CodecName) == 0) { - webrtc::VideoCodecVP8* settings = new webrtc::VideoCodecVP8(); - settings->resilience = webrtc::kResilientStream; - settings->numberOfTemporalLayers = 1; + webrtc::VideoCodecVP8* settings = new webrtc::VideoCodecVP8( + webrtc::VideoEncoder::GetDefaultVp8Settings()); options.video_noise_reduction.Get(&settings->denoisingOn); - settings->errorConcealmentOn = false; - settings->automaticResizeOn = false; - settings->frameDroppingOn = true; - settings->keyFrameInterval = 3000; return settings; } return NULL; @@ -233,13 +229,9 @@ void WebRtcVideoEncoderFactory2::DestroyVideoEncoderSettings( if (encoder_settings == NULL) { return; } - if (_stricmp(codec.name.c_str(), kVp8CodecName) == 0) { delete reinterpret_cast<webrtc::VideoCodecVP8*>(encoder_settings); - return; } - // We should be able to destroy all encoder settings we've allocated. - assert(false); } bool WebRtcVideoEncoderFactory2::SupportsCodec(const VideoCodec& codec) { @@ -283,37 +275,19 @@ void DefaultUnsignalledSsrcHandler::SetDefaultRenderer( } WebRtcVideoEngine2::WebRtcVideoEngine2() - : default_codec_format_(kDefaultVideoCodecPref.width, - kDefaultVideoCodecPref.height, - FPS_TO_INTERVAL(kDefaultFramerate), - FOURCC_ANY) { - // Construct without a factory or voice engine. - Construct(NULL, NULL, new rtc::CpuMonitor(NULL)); -} - -WebRtcVideoEngine2::WebRtcVideoEngine2( - WebRtcVideoChannelFactory* channel_factory) - : default_codec_format_(kDefaultVideoCodecPref.width, + : worker_thread_(NULL), + voice_engine_(NULL), + video_codecs_(DefaultVideoCodecs()), + default_codec_format_(kDefaultVideoCodecPref.width, kDefaultVideoCodecPref.height, FPS_TO_INTERVAL(kDefaultFramerate), - FOURCC_ANY) { - // Construct without a voice engine. - Construct(channel_factory, NULL, new rtc::CpuMonitor(NULL)); -} - -void WebRtcVideoEngine2::Construct(WebRtcVideoChannelFactory* channel_factory, - WebRtcVoiceEngine* voice_engine, - rtc::CpuMonitor* cpu_monitor) { - LOG(LS_INFO) << "WebRtcVideoEngine2::WebRtcVideoEngine2"; - worker_thread_ = NULL; - voice_engine_ = voice_engine; - initialized_ = false; - capture_started_ = false; - cpu_monitor_.reset(cpu_monitor); - channel_factory_ = channel_factory; - - video_codecs_ = DefaultVideoCodecs(); - + FOURCC_ANY), + initialized_(false), + cpu_monitor_(new rtc::CpuMonitor(NULL)), + channel_factory_(NULL), + external_decoder_factory_(NULL), + external_encoder_factory_(NULL) { + LOG(LS_INFO) << "WebRtcVideoEngine2::WebRtcVideoEngine2()"; rtp_header_extensions_.push_back( RtpHeaderExtension(kRtpTimestampOffsetHeaderExtension, kRtpTimestampOffsetHeaderExtensionDefaultId)); @@ -322,6 +296,11 @@ void WebRtcVideoEngine2::Construct(WebRtcVideoChannelFactory* channel_factory, kRtpAbsoluteSenderTimeHeaderExtensionDefaultId)); } +void WebRtcVideoEngine2::SetChannelFactory( + WebRtcVideoChannelFactory* channel_factory) { + channel_factory_ = channel_factory; +} + WebRtcVideoEngine2::~WebRtcVideoEngine2() { LOG(LS_INFO) << "WebRtcVideoEngine2::~WebRtcVideoEngine2"; @@ -355,14 +334,6 @@ void WebRtcVideoEngine2::Terminate() { int WebRtcVideoEngine2::GetCapabilities() { return VIDEO_RECV | VIDEO_SEND; } -bool WebRtcVideoEngine2::SetOptions(const VideoOptions& options) { - // TODO(pbos): Do we need this? This is a no-op in the existing - // WebRtcVideoEngine implementation. - LOG(LS_VERBOSE) << "SetOptions: " << options.ToString(); - // options_ = options; - return true; -} - bool WebRtcVideoEngine2::SetDefaultEncoderConfig( const VideoEncoderConfig& config) { const VideoCodec& codec = config.max_codec; @@ -424,14 +395,32 @@ void WebRtcVideoEngine2::SetLogging(int min_sev, const char* filter) { } } -bool WebRtcVideoEngine2::EnableTimedRender() { - // TODO(pbos): Figure out whether this can be removed. - return true; +void WebRtcVideoEngine2::SetExternalDecoderFactory( + WebRtcVideoDecoderFactory* decoder_factory) { + external_decoder_factory_ = decoder_factory; } -bool WebRtcVideoEngine2::SetLocalRenderer(VideoRenderer* renderer) { - // TODO(pbos): Implement or remove. Unclear which stream should be rendered - // locally even. +void WebRtcVideoEngine2::SetExternalEncoderFactory( + WebRtcVideoEncoderFactory* encoder_factory) { + if (external_encoder_factory_ == encoder_factory) { + return; + } + if (external_encoder_factory_) { + external_encoder_factory_->RemoveObserver(this); + } + external_encoder_factory_ = encoder_factory; + if (external_encoder_factory_) { + external_encoder_factory_->AddObserver(this); + } + + // Invoke OnCodecAvailable() here in case the list of codecs is already + // available when the encoder factory is installed. If not the encoder + // factory will invoke the callback later when the codecs become available. + OnCodecsAvailable(); +} + +bool WebRtcVideoEngine2::EnableTimedRender() { + // TODO(pbos): Figure out whether this can be removed. return true; } @@ -517,6 +506,9 @@ WebRtcVideoEncoderFactory2* WebRtcVideoEngine2::GetVideoEncoderFactory() { return &default_video_encoder_factory_; } +void WebRtcVideoEngine2::OnCodecsAvailable() { + // TODO(pbos): Implement. +} // Thin map between VideoFrame and an existing webrtc::I420VideoFrame // to avoid having to copy the rendered VideoFrame prematurely. // This implementation is only safe to use in a const context and should never @@ -679,8 +671,8 @@ WebRtcVideoChannel2::WebRtcVideoChannel2( WebRtcVideoEngine2* engine, VoiceMediaChannel* voice_channel, WebRtcVideoEncoderFactory2* encoder_factory) - : encoder_factory_(encoder_factory), - unsignalled_ssrc_handler_(&default_unsignalled_ssrc_handler_) { + : unsignalled_ssrc_handler_(&default_unsignalled_ssrc_handler_), + encoder_factory_(encoder_factory) { // TODO(pbos): Connect the video and audio with |voice_channel|. webrtc::Call::Config config(this); Construct(webrtc::Call::Create(config), engine); @@ -690,8 +682,8 @@ WebRtcVideoChannel2::WebRtcVideoChannel2( webrtc::Call* call, WebRtcVideoEngine2* engine, WebRtcVideoEncoderFactory2* encoder_factory) - : encoder_factory_(encoder_factory), - unsignalled_ssrc_handler_(&default_unsignalled_ssrc_handler_) { + : unsignalled_ssrc_handler_(&default_unsignalled_ssrc_handler_), + encoder_factory_(encoder_factory) { Construct(call, engine); } @@ -1174,7 +1166,9 @@ void WebRtcVideoChannel2::OnRtcpReceived( } void WebRtcVideoChannel2::OnReadyToSend(bool ready) { - LOG(LS_VERBOSE) << "OnReadySend: " << (ready ? "Ready." : "Not ready."); + LOG(LS_VERBOSE) << "OnReadyToSend: " << (ready ? "Ready." : "Not ready."); + call_->SignalNetworkState(ready ? webrtc::Call::kNetworkUp + : webrtc::Call::kNetworkDown); } bool WebRtcVideoChannel2::MuteStream(uint32 ssrc, bool mute) { @@ -1315,10 +1309,10 @@ WebRtcVideoChannel2::WebRtcVideoSendStream::WebRtcVideoSendStream( const StreamParams& sp, const std::vector<webrtc::RtpExtension>& rtp_extensions) : call_(call), - parameters_(webrtc::VideoSendStream::Config(), options, codec_settings), encoder_factory_(encoder_factory), - capturer_(NULL), stream_(NULL), + parameters_(webrtc::VideoSendStream::Config(), options, codec_settings), + capturer_(NULL), sending_(false), muted_(false) { parameters_.config.rtp.max_packet_size = kVideoMtu; @@ -1385,16 +1379,10 @@ void WebRtcVideoChannel2::WebRtcVideoSendStream::InputFrame( const VideoFrame* frame) { LOG(LS_VERBOSE) << "InputFrame: " << frame->GetWidth() << "x" << frame->GetHeight(); - bool is_screencast = capturer->IsScreencast(); // Lock before copying, can be called concurrently when swapping input source. rtc::CritScope frame_cs(&frame_lock_); - if (!muted_) { - ConvertToI420VideoFrame(*frame, &video_frame_); - } else { - // Create a tiny black frame to transmit instead. - CreateBlackFrame(&video_frame_, 1, 1); - is_screencast = false; - } + ConvertToI420VideoFrame(*frame, &video_frame_); + rtc::CritScope cs(&lock_); if (stream_ == NULL) { LOG(LS_WARNING) << "Capturer inputting frames before send codecs are " @@ -1406,14 +1394,20 @@ void WebRtcVideoChannel2::WebRtcVideoSendStream::InputFrame( LOG(LS_VERBOSE) << "VideoFormat 0x0 set, Dropping frame."; return; } - // Reconfigure codec if necessary. - if (is_screencast) { - SetDimensions(video_frame_.width(), video_frame_.height()); + if (muted_) { + // Create a black frame to transmit instead. + CreateBlackFrame(&video_frame_, + static_cast<int>(frame->GetWidth()), + static_cast<int>(frame->GetHeight())); } + // Reconfigure codec if necessary. + SetDimensions( + video_frame_.width(), video_frame_.height(), capturer->IsScreencast()); + LOG(LS_VERBOSE) << "SwapFrame: " << video_frame_.width() << "x" << video_frame_.height() << " -> (codec) " - << parameters_.video_streams.back().width << "x" - << parameters_.video_streams.back().height; + << parameters_.encoder_config.streams.back().width << "x" + << parameters_.encoder_config.streams.back().height; stream_->Input()->SwapFrame(&video_frame_); } @@ -1437,7 +1431,7 @@ bool WebRtcVideoChannel2::WebRtcVideoSendStream::SetCapturer( black_frame.CreateEmptyFrame( width, height, width, half_width, half_width); SetWebRtcFrameToBlack(&black_frame); - SetDimensions(width, height); + SetDimensions(width, height, false); stream_->Input()->SwapFrame(&black_frame); } @@ -1469,9 +1463,9 @@ bool WebRtcVideoChannel2::WebRtcVideoSendStream::SetVideoFormat( << parameters_.config.rtp.ssrcs[0] << "."; } else { // TODO(pbos): Fix me, this only affects the last stream! - parameters_.video_streams.back().max_framerate = + parameters_.encoder_config.streams.back().max_framerate = VideoFormat::IntervalToFps(format.interval); - SetDimensions(format.width, format.height); + SetDimensions(format.width, format.height, false); } format_ = format; @@ -1517,7 +1511,7 @@ void WebRtcVideoChannel2::WebRtcVideoSendStream::SetCodecAndOptions( if (video_streams.empty()) { return; } - parameters_.video_streams = video_streams; + parameters_.encoder_config.streams = video_streams; format_ = VideoFormat(codec_settings.codec.width, codec_settings.codec.height, VideoFormat::FpsToInterval(30), @@ -1560,35 +1554,55 @@ void WebRtcVideoChannel2::WebRtcVideoSendStream::SetRtpExtensions( RecreateWebRtcStream(); } -void WebRtcVideoChannel2::WebRtcVideoSendStream::SetDimensions(int width, - int height) { - assert(!parameters_.video_streams.empty()); +void WebRtcVideoChannel2::WebRtcVideoSendStream::SetDimensions( + int width, + int height, + bool override_max) { + assert(!parameters_.encoder_config.streams.empty()); LOG(LS_VERBOSE) << "SetDimensions: " << width << "x" << height; - if (parameters_.video_streams.back().width == width && - parameters_.video_streams.back().height == height) { + + VideoCodecSettings codec_settings; + parameters_.codec_settings.Get(&codec_settings); + // Restrict dimensions according to codec max. + if (!override_max) { + if (codec_settings.codec.width < width) + width = codec_settings.codec.width; + if (codec_settings.codec.height < height) + height = codec_settings.codec.height; + } + + if (parameters_.encoder_config.streams.back().width == width && + parameters_.encoder_config.streams.back().height == height) { return; } - // TODO(pbos): Fix me, this only affects the last stream! - parameters_.video_streams.back().width = width; - parameters_.video_streams.back().height = height; + webrtc::VideoEncoderConfig encoder_config = parameters_.encoder_config; + encoder_config.encoder_specific_settings = + encoder_factory_->CreateVideoEncoderSettings(codec_settings.codec, + parameters_.options); - VideoCodecSettings codec_settings; - parameters_.codec_settings.Get(&codec_settings); - void* encoder_settings = encoder_factory_->CreateVideoEncoderSettings( - codec_settings.codec, parameters_.options); + VideoCodec codec = codec_settings.codec; + codec.width = width; + codec.height = height; + + encoder_config.streams = encoder_factory_->CreateVideoStreams( + codec, parameters_.options, parameters_.config.rtp.ssrcs.size()); - bool stream_reconfigured = stream_->ReconfigureVideoEncoder( - parameters_.video_streams, encoder_settings); + bool stream_reconfigured = stream_->ReconfigureVideoEncoder(encoder_config); - encoder_factory_->DestroyVideoEncoderSettings(codec_settings.codec, - encoder_settings); + encoder_factory_->DestroyVideoEncoderSettings( + codec_settings.codec, + encoder_config.encoder_specific_settings); + + encoder_config.encoder_specific_settings = NULL; if (!stream_reconfigured) { LOG(LS_WARNING) << "Failed to reconfigure video encoder for dimensions: " << width << "x" << height; return; } + + parameters_.encoder_config = encoder_config; } void WebRtcVideoChannel2::WebRtcVideoSendStream::Start() { @@ -1652,9 +1666,9 @@ WebRtcVideoChannel2::WebRtcVideoSendStream::GetVideoSenderInfo() { info.input_frame_width = last_captured_frame_format.width; info.input_frame_height = last_captured_frame_format.height; info.send_frame_width = - static_cast<int>(parameters_.video_streams.front().width); + static_cast<int>(parameters_.encoder_config.streams.front().width); info.send_frame_height = - static_cast<int>(parameters_.video_streams.front().height); + static_cast<int>(parameters_.encoder_config.streams.front().height); } // TODO(pbos): Support or remove the following stats. @@ -1671,14 +1685,18 @@ void WebRtcVideoChannel2::WebRtcVideoSendStream::RecreateWebRtcStream() { VideoCodecSettings codec_settings; parameters_.codec_settings.Get(&codec_settings); - void* encoder_settings = encoder_factory_->CreateVideoEncoderSettings( - codec_settings.codec, parameters_.options); + parameters_.encoder_config.encoder_specific_settings = + encoder_factory_->CreateVideoEncoderSettings(codec_settings.codec, + parameters_.options); - stream_ = call_->CreateVideoSendStream( - parameters_.config, parameters_.video_streams, encoder_settings); + stream_ = call_->CreateVideoSendStream(parameters_.config, + parameters_.encoder_config); - encoder_factory_->DestroyVideoEncoderSettings(codec_settings.codec, - encoder_settings); + encoder_factory_->DestroyVideoEncoderSettings( + codec_settings.codec, + parameters_.encoder_config.encoder_specific_settings); + + parameters_.encoder_config.encoder_specific_settings = NULL; if (sending_) { stream_->Start(); @@ -1690,11 +1708,11 @@ WebRtcVideoChannel2::WebRtcVideoReceiveStream::WebRtcVideoReceiveStream( const webrtc::VideoReceiveStream::Config& config, const std::vector<VideoCodecSettings>& recv_codecs) : call_(call), - config_(config), stream_(NULL), + config_(config), + renderer_(NULL), last_width_(-1), - last_height_(-1), - renderer_(NULL) { + last_height_(-1) { config_.renderer = this; // SetRecvCodecs will also reset (start) the VideoReceiveStream. SetRecvCodecs(recv_codecs); diff --git a/media/webrtc/webrtcvideoengine2.h b/media/webrtc/webrtcvideoengine2.h index c7eb6a4..18a80d7 100644 --- a/media/webrtc/webrtcvideoengine2.h +++ b/media/webrtc/webrtcvideoengine2.h @@ -34,10 +34,12 @@ #include "talk/media/base/mediaengine.h" #include "talk/media/webrtc/webrtcvideochannelfactory.h" +#include "talk/media/webrtc/webrtcvideodecoderfactory.h" +#include "talk/media/webrtc/webrtcvideoencoderfactory.h" #include "webrtc/base/cpumonitor.h" #include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/thread_annotations.h" #include "webrtc/common_video/interface/i420_video_frame.h" -#include "webrtc/system_wrappers/interface/thread_annotations.h" #include "webrtc/transport.h" #include "webrtc/video_receive_stream.h" #include "webrtc/video_renderer.h" @@ -65,14 +67,13 @@ class VideoFrame; class VideoProcessor; class VideoRenderer; class VoiceMediaChannel; -class WebRtcVideoChannel2; class WebRtcDecoderObserver; class WebRtcEncoderObserver; class WebRtcLocalStreamInfo; class WebRtcRenderAdapter; +class WebRtcVideoChannel2; class WebRtcVideoChannelRecvInfo; class WebRtcVideoChannelSendInfo; -class WebRtcVideoDecoderFactory; class WebRtcVoiceEngine; struct CapturedFrame; @@ -119,9 +120,8 @@ class WebRtcVideoEncoderFactory2 { const VideoCodec& codec, const VideoOptions& options); - virtual void* CreateVideoEncoderSettings( - const VideoCodec& codec, - const VideoOptions& options); + virtual void* CreateVideoEncoderSettings(const VideoCodec& codec, + const VideoOptions& options); virtual void DestroyVideoEncoderSettings(const VideoCodec& codec, void* encoder_settings); @@ -130,20 +130,21 @@ class WebRtcVideoEncoderFactory2 { }; // WebRtcVideoEngine2 is used for the new native WebRTC Video API (webrtc:1667). -class WebRtcVideoEngine2 : public sigslot::has_slots<> { +class WebRtcVideoEngine2 : public sigslot::has_slots<>, + public WebRtcVideoEncoderFactory::Observer { public: // Creates the WebRtcVideoEngine2 with internal VideoCaptureModule. WebRtcVideoEngine2(); - // Custom WebRtcVideoChannelFactory for testing purposes. - explicit WebRtcVideoEngine2(WebRtcVideoChannelFactory* channel_factory); - ~WebRtcVideoEngine2(); + virtual ~WebRtcVideoEngine2(); + + // Use a custom WebRtcVideoChannelFactory (for testing purposes). + void SetChannelFactory(WebRtcVideoChannelFactory* channel_factory); // Basic video engine implementation. bool Init(rtc::Thread* worker_thread); void Terminate(); int GetCapabilities(); - bool SetOptions(const VideoOptions& options); bool SetDefaultEncoderConfig(const VideoEncoderConfig& config); VideoEncoderConfig GetDefaultEncoderConfig() const; @@ -153,20 +154,23 @@ class WebRtcVideoEngine2 : public sigslot::has_slots<> { const std::vector<RtpHeaderExtension>& rtp_header_extensions() const; void SetLogging(int min_sev, const char* filter); + // Set a WebRtcVideoDecoderFactory for external decoding. Video engine does + // not take the ownership of |decoder_factory|. The caller needs to make sure + // that |decoder_factory| outlives the video engine. + void SetExternalDecoderFactory(WebRtcVideoDecoderFactory* decoder_factory); + // Set a WebRtcVideoEncoderFactory for external encoding. Video engine does + // not take the ownership of |encoder_factory|. The caller needs to make sure + // that |encoder_factory| outlives the video engine. + virtual void SetExternalEncoderFactory( + WebRtcVideoEncoderFactory* encoder_factory); + bool EnableTimedRender(); - // No-op, never used. - bool SetLocalRenderer(VideoRenderer* renderer); // This is currently ignored. sigslot::repeater2<VideoCapturer*, CaptureState> SignalCaptureStateChange; // Set the VoiceEngine for A/V sync. This can only be called before Init. bool SetVoiceEngine(WebRtcVoiceEngine* voice_engine); - // Functions called by WebRtcVideoChannel2. - const VideoFormat& default_codec_format() const { - return default_codec_format_; - } - bool FindCodec(const VideoCodec& in); bool CanSendCodec(const VideoCodec& in, const VideoCodec& current, @@ -181,9 +185,7 @@ class WebRtcVideoEngine2 : public sigslot::has_slots<> { virtual WebRtcVideoEncoderFactory2* GetVideoEncoderFactory(); private: - void Construct(WebRtcVideoChannelFactory* channel_factory, - WebRtcVoiceEngine* voice_engine, - rtc::CpuMonitor* cpu_monitor); + virtual void OnCodecsAvailable() OVERRIDE; rtc::Thread* worker_thread_; WebRtcVoiceEngine* voice_engine_; @@ -193,8 +195,6 @@ class WebRtcVideoEngine2 : public sigslot::has_slots<> { bool initialized_; - bool capture_started_; - // Critical section to protect the media processor register/unregister // while processing a frame rtc::CriticalSection signal_media_critical_; @@ -202,6 +202,9 @@ class WebRtcVideoEngine2 : public sigslot::has_slots<> { rtc::scoped_ptr<rtc::CpuMonitor> cpu_monitor_; WebRtcVideoChannelFactory* channel_factory_; WebRtcVideoEncoderFactory2 default_video_encoder_factory_; + + WebRtcVideoDecoderFactory* external_decoder_factory_; + WebRtcVideoEncoderFactory* external_encoder_factory_; }; class WebRtcVideoChannel2 : public rtc::MessageHandler, @@ -328,13 +331,16 @@ class WebRtcVideoChannel2 : public rtc::MessageHandler, // Sent resolutions + bitrates etc. by the underlying VideoSendStream, // typically changes when setting a new resolution or reconfiguring // bitrates. - std::vector<webrtc::VideoStream> video_streams; + webrtc::VideoEncoderConfig encoder_config; }; void SetCodecAndOptions(const VideoCodecSettings& codec, - const VideoOptions& options); - void RecreateWebRtcStream(); - void SetDimensions(int width, int height); + const VideoOptions& options) + EXCLUSIVE_LOCKS_REQUIRED(lock_); + void RecreateWebRtcStream() EXCLUSIVE_LOCKS_REQUIRED(lock_); + // When |override_max| is false constrain width/height to codec dimensions. + void SetDimensions(int width, int height, bool override_max) + EXCLUSIVE_LOCKS_REQUIRED(lock_); webrtc::Call* const call_; WebRtcVideoEncoderFactory2* const encoder_factory_; diff --git a/media/webrtc/webrtcvideoengine2_unittest.cc b/media/webrtc/webrtcvideoengine2_unittest.cc index 9c313e6..6112c50 100644 --- a/media/webrtc/webrtcvideoengine2_unittest.cc +++ b/media/webrtc/webrtcvideoengine2_unittest.cc @@ -68,13 +68,10 @@ void VerifyCodecHasDefaultFeedbackParams(const cricket::VideoCodec& codec) { namespace cricket { FakeVideoSendStream::FakeVideoSendStream( const webrtc::VideoSendStream::Config& config, - const std::vector<webrtc::VideoStream>& video_streams, - const void* encoder_settings) - : sending_(false), - config_(config), - codec_settings_set_(false) { + const webrtc::VideoEncoderConfig& encoder_config) + : sending_(false), config_(config), codec_settings_set_(false) { assert(config.encoder_settings.encoder != NULL); - ReconfigureVideoEncoder(video_streams, encoder_settings); + ReconfigureVideoEncoder(encoder_config); } webrtc::VideoSendStream::Config FakeVideoSendStream::GetConfig() { @@ -82,7 +79,7 @@ webrtc::VideoSendStream::Config FakeVideoSendStream::GetConfig() { } std::vector<webrtc::VideoStream> FakeVideoSendStream::GetVideoStreams() { - return video_streams_; + return encoder_config_.streams; } bool FakeVideoSendStream::IsSending() const { @@ -104,15 +101,14 @@ webrtc::VideoSendStream::Stats FakeVideoSendStream::GetStats() const { } bool FakeVideoSendStream::ReconfigureVideoEncoder( - const std::vector<webrtc::VideoStream>& streams, - const void* encoder_specific) { - video_streams_ = streams; - if (encoder_specific != NULL) { + const webrtc::VideoEncoderConfig& config) { + encoder_config_ = config; + if (config.encoder_specific_settings != NULL) { assert(config_.encoder_settings.payload_name == "VP8"); - vp8_settings_ = - *reinterpret_cast<const webrtc::VideoCodecVP8*>(encoder_specific); + vp8_settings_ = *reinterpret_cast<const webrtc::VideoCodecVP8*>( + config.encoder_specific_settings); } - codec_settings_set_ = encoder_specific != NULL; + codec_settings_set_ = config.encoder_specific_settings != NULL; return true; } @@ -157,7 +153,9 @@ void FakeVideoReceiveStream::Stop() { void FakeVideoReceiveStream::GetCurrentReceiveCodec(webrtc::VideoCodec* codec) { } -FakeCall::FakeCall() { SetVideoCodecs(GetDefaultVideoCodecs()); } +FakeCall::FakeCall() : network_state_(kNetworkUp) { + SetVideoCodecs(GetDefaultVideoCodecs()); +} FakeCall::~FakeCall() { EXPECT_EQ(0u, video_send_streams_.size()); @@ -218,12 +216,15 @@ std::vector<webrtc::VideoCodec> FakeCall::GetDefaultVideoCodecs() { return codecs; } +webrtc::Call::NetworkState FakeCall::GetNetworkState() const { + return network_state_; +} + webrtc::VideoSendStream* FakeCall::CreateVideoSendStream( const webrtc::VideoSendStream::Config& config, - const std::vector<webrtc::VideoStream>& video_streams, - const void* encoder_settings) { + const webrtc::VideoEncoderConfig& encoder_config) { FakeVideoSendStream* fake_stream = - new FakeVideoSendStream(config, video_streams, encoder_settings); + new FakeVideoSendStream(config, encoder_config); video_send_streams_.push_back(fake_stream); return fake_stream; } @@ -274,6 +275,10 @@ uint32_t FakeCall::ReceiveBitrateEstimate() { return 0; } +void FakeCall::SignalNetworkState(webrtc::Call::NetworkState state) { + network_state_ = state; +} + FakeWebRtcVideoChannel2::FakeWebRtcVideoChannel2( FakeCall* call, WebRtcVideoEngine2* engine, @@ -289,6 +294,7 @@ FakeWebRtcVideoChannel2::~FakeWebRtcVideoChannel2() { VoiceMediaChannel* FakeWebRtcVideoChannel2::GetVoiceChannel() { return voice_channel_; } + FakeCall* FakeWebRtcVideoChannel2::GetFakeCall() { return fake_call_; } @@ -309,7 +315,8 @@ WebRtcVideoChannel2* FakeWebRtcVideoMediaChannelFactory::Create( class WebRtcVideoEngine2Test : public testing::Test { public: - WebRtcVideoEngine2Test() : engine_(&factory_) { + WebRtcVideoEngine2Test() { + engine_.SetChannelFactory(&factory_); std::vector<VideoCodec> engine_codecs = engine_.codecs(); assert(!engine_codecs.empty()); bool codec_set = false; @@ -535,15 +542,17 @@ WEBRTC_BASE_TEST(AdaptResolution16x10); WEBRTC_BASE_TEST(AdaptResolution4x3); -WEBRTC_BASE_TEST(MuteStream); - -WEBRTC_BASE_TEST(MultipleSendStreams); - // TODO(juberti): Restore this test once we support sending 0 fps. WEBRTC_DISABLED_BASE_TEST(AdaptDropAllFrames); // TODO(juberti): Understand why we get decode errors on this test. WEBRTC_DISABLED_BASE_TEST(AdaptFramerate); +WEBRTC_BASE_TEST(SendsLowerResolutionOnSmallerFrames); + +WEBRTC_BASE_TEST(MuteStream); + +WEBRTC_BASE_TEST(MultipleSendStreams); + WEBRTC_BASE_TEST(SetSendStreamFormat0x0); // TODO(zhurunz): Fix the flakey test. @@ -1611,8 +1620,17 @@ TEST_F(WebRtcVideoChannel2Test, DISABLED_UpdateEncoderCodecsAfterSetFactory) { FAIL() << "Not implemented."; // TODO(pbos): Implement. } -TEST_F(WebRtcVideoChannel2Test, DISABLED_OnReadyToSend) { - FAIL() << "Not implemented."; // TODO(pbos): Implement. +TEST_F(WebRtcVideoChannel2Test, OnReadyToSendSignalsNetworkState) { + EXPECT_EQ(webrtc::Call::kNetworkUp, + fake_channel_->GetFakeCall()->GetNetworkState()); + + channel_->OnReadyToSend(false); + EXPECT_EQ(webrtc::Call::kNetworkDown, + fake_channel_->GetFakeCall()->GetNetworkState()); + + channel_->OnReadyToSend(true); + EXPECT_EQ(webrtc::Call::kNetworkUp, + fake_channel_->GetFakeCall()->GetNetworkState()); } TEST_F(WebRtcVideoChannel2Test, DISABLED_CaptureFrameTimestampToNtpTimestamp) { diff --git a/media/webrtc/webrtcvideoengine2_unittest.h b/media/webrtc/webrtcvideoengine2_unittest.h index 54e6f06..30f1efb 100644 --- a/media/webrtc/webrtcvideoengine2_unittest.h +++ b/media/webrtc/webrtcvideoengine2_unittest.h @@ -39,8 +39,7 @@ namespace cricket { class FakeVideoSendStream : public webrtc::VideoSendStream { public: FakeVideoSendStream(const webrtc::VideoSendStream::Config& config, - const std::vector<webrtc::VideoStream>& video_streams, - const void* encoder_settings); + const webrtc::VideoEncoderConfig& encoder_config); webrtc::VideoSendStream::Config GetConfig(); std::vector<webrtc::VideoStream> GetVideoStreams(); @@ -51,8 +50,7 @@ class FakeVideoSendStream : public webrtc::VideoSendStream { virtual webrtc::VideoSendStream::Stats GetStats() const OVERRIDE; virtual bool ReconfigureVideoEncoder( - const std::vector<webrtc::VideoStream>& streams, - const void* encoder_specific); + const webrtc::VideoEncoderConfig& config) OVERRIDE; virtual webrtc::VideoSendStreamInput* Input() OVERRIDE; @@ -61,7 +59,7 @@ class FakeVideoSendStream : public webrtc::VideoSendStream { bool sending_; webrtc::VideoSendStream::Config config_; - std::vector<webrtc::VideoStream> video_streams_; + webrtc::VideoEncoderConfig encoder_config_; bool codec_settings_set_; webrtc::VideoCodecVP8 vp8_settings_; }; @@ -103,11 +101,12 @@ class FakeCall : public webrtc::Call { std::vector<webrtc::VideoCodec> GetDefaultVideoCodecs(); + webrtc::Call::NetworkState GetNetworkState() const; + private: virtual webrtc::VideoSendStream* CreateVideoSendStream( const webrtc::VideoSendStream::Config& config, - const std::vector<webrtc::VideoStream>& video_streams, - const void* encoder_settings) OVERRIDE; + const webrtc::VideoEncoderConfig& encoder_config) OVERRIDE; virtual void DestroyVideoSendStream( webrtc::VideoSendStream* send_stream) OVERRIDE; @@ -122,6 +121,9 @@ class FakeCall : public webrtc::Call { virtual uint32_t SendBitrateEstimate() OVERRIDE; virtual uint32_t ReceiveBitrateEstimate() OVERRIDE; + virtual void SignalNetworkState(webrtc::Call::NetworkState state) OVERRIDE; + + webrtc::Call::NetworkState network_state_; std::vector<webrtc::VideoCodec> codecs_; std::vector<FakeVideoSendStream*> video_send_streams_; std::vector<FakeVideoReceiveStream*> video_receive_streams_; diff --git a/media/webrtc/webrtcvideoengine_unittest.cc b/media/webrtc/webrtcvideoengine_unittest.cc index 11edd05..35f05e1 100644 --- a/media/webrtc/webrtcvideoengine_unittest.cc +++ b/media/webrtc/webrtcvideoengine_unittest.cc @@ -26,22 +26,16 @@ */ #include "talk/media/base/constants.h" -#include "talk/media/base/fakemediaprocessor.h" #include "talk/media/base/fakenetworkinterface.h" -#include "talk/media/base/fakevideorenderer.h" #include "talk/media/base/mediachannel.h" #include "talk/media/base/testutils.h" -#include "talk/media/base/videoadapter.h" #include "talk/media/base/videoengine_unittest.h" -#include "talk/media/webrtc/fakewebrtcvideocapturemodule.h" #include "talk/media/webrtc/fakewebrtcvideoengine.h" -#include "talk/media/webrtc/fakewebrtcvoiceengine.h" #include "webrtc/base/fakecpumonitor.h" #include "webrtc/base/gunit.h" #include "webrtc/base/logging.h" #include "webrtc/base/scoped_ptr.h" #include "webrtc/base/stream.h" -#include "talk/media/webrtc/webrtcvideocapturer.h" #include "talk/media/webrtc/webrtcvideoengine.h" #include "talk/media/webrtc/webrtcvideoframe.h" #include "talk/media/webrtc/webrtcvoiceengine.h" @@ -54,9 +48,6 @@ using cricket::kRtpTimestampOffsetHeaderExtension; using cricket::kRtpAbsoluteSenderTimeHeaderExtension; static const cricket::VideoCodec kVP8Codec720p(100, "VP8", 1280, 720, 30, 0); -static const cricket::VideoCodec kVP8Codec360p(100, "VP8", 640, 360, 30, 0); -static const cricket::VideoCodec kVP8Codec270p(100, "VP8", 480, 270, 30, 0); -static const cricket::VideoCodec kVP8Codec180p(100, "VP8", 320, 180, 30, 0); static const cricket::VideoCodec kVP8Codec(100, "VP8", 640, 400, 30, 0); static const cricket::VideoCodec kH264Codec(127, "H264", 640, 400, 30, 0); @@ -78,7 +69,6 @@ static const uint32 kSsrcs3[] = {1, 2, 3}; static const uint32 kRtxSsrcs1[] = {4}; static const uint32 kRtxSsrcs3[] = {4, 5, 6}; - class FakeViEWrapper : public cricket::ViEWrapper { public: explicit FakeViEWrapper(cricket::FakeWebRtcVideoEngine* engine) @@ -1556,7 +1546,6 @@ TEST_F(WebRtcVideoEngineTestFake, SetBandwidthInConference) { 768, kMinBandwidthKbps, kStartBandwidthKbps); } - // Test that sending screencast frames doesn't change bitrate. TEST_F(WebRtcVideoEngineTestFake, SetBandwidthScreencast) { EXPECT_TRUE(SetupEngine()); @@ -1576,7 +1565,6 @@ TEST_F(WebRtcVideoEngineTestFake, SetBandwidthScreencast) { VerifyVP8SendCodec(channel_num, kVP8Codec.width, kVP8Codec.height, 0, 111); } - // Test SetSendSsrc. TEST_F(WebRtcVideoEngineTestFake, SetSendSsrcAndCname) { EXPECT_TRUE(SetupEngine()); @@ -1597,7 +1585,6 @@ TEST_F(WebRtcVideoEngineTestFake, SetSendSsrcAndCname) { EXPECT_STREQ("cname", rtcp_cname); } - // Test that the local SSRC is the same on sending and receiving channels if the // receive channel is created before the send channel. TEST_F(WebRtcVideoEngineTestFake, SetSendSsrcAfterCreatingReceiveChannel) { @@ -1618,7 +1605,6 @@ TEST_F(WebRtcVideoEngineTestFake, SetSendSsrcAfterCreatingReceiveChannel) { EXPECT_EQ(1, vie_.GetNumSsrcs(receive_channel_num)); } - // Test SetOptions with denoising flag. TEST_F(WebRtcVideoEngineTestFake, SetOptionsWithDenoising) { EXPECT_TRUE(SetupEngine()); @@ -1707,7 +1693,6 @@ TEST_F(WebRtcVideoEngineTestFake, MultipleSendStreamsWithOneCapturer) { EXPECT_EQ(-1, vie_.GetIncomingFrameNum(channel1)); } - TEST_F(WebRtcVideoEngineTestFake, SendReceiveBitratesStats) { EXPECT_TRUE(SetupEngine()); cricket::VideoOptions options; @@ -1839,7 +1824,6 @@ TEST_F(WebRtcVideoEngineTestFake, TestSetInvalidCpuThreshold) { EXPECT_EQ(high, 1.0f); } - TEST_F(WebRtcVideoEngineTestFake, ResetCodecOnScreencast) { EXPECT_TRUE(SetupEngine()); cricket::VideoOptions options; @@ -1870,7 +1854,6 @@ TEST_F(WebRtcVideoEngineTestFake, ResetCodecOnScreencast) { EXPECT_FALSE(gcodec.codecSpecific.VP8.denoisingOn); } - TEST_F(WebRtcVideoEngineTestFake, DontRegisterDecoderIfFactoryIsNotGiven) { engine_.SetExternalDecoderFactory(NULL); EXPECT_TRUE(SetupEngine()); @@ -2397,9 +2380,6 @@ TEST_F(WebRtcVideoMediaChannelTest, SetRecvCodecsUnsupportedCodec) { EXPECT_FALSE(channel_->SetRecvCodecs(codecs)); } -// Disable for TSan v2, see -// https://code.google.com/p/webrtc/issues/detail?id=3671 for details. -#if !defined(THREAD_SANITIZER) TEST_F(WebRtcVideoMediaChannelTest, GetRtpSendTimeExtension) { // Enable RTP timestamp extension. const int id = 12; @@ -2411,7 +2391,6 @@ TEST_F(WebRtcVideoMediaChannelTest, GetRtpSendTimeExtension) { EXPECT_TRUE(channel_->SetSendRtpHeaderExtensions(extensions)); EXPECT_EQ(id, channel_->GetRtpSendTimeExtnId()); } -#endif // if !defined(THREAD_SANITIZER) TEST_F(WebRtcVideoMediaChannelTest, SetSend) { Base::SetSend(); @@ -2574,7 +2553,6 @@ TEST_F(WebRtcVideoMediaChannelTest, RejectEmptyStreamParams) { Base::RejectEmptyStreamParams(); } - TEST_F(WebRtcVideoMediaChannelTest, AdaptResolution16x10) { Base::AdaptResolution16x10(); } diff --git a/media/webrtc/webrtcvideoframe_unittest.cc b/media/webrtc/webrtcvideoframe_unittest.cc index 4cfe7c0..5f65c58 100644 --- a/media/webrtc/webrtcvideoframe_unittest.cc +++ b/media/webrtc/webrtcvideoframe_unittest.cc @@ -27,14 +27,10 @@ #include "talk/media/base/videoframe_unittest.h" #include "talk/media/webrtc/webrtcvideoframe.h" -#include "webrtc/base/flags.h" - -extern int FLAG_yuvconverter_repeat; // From lmivideoframe_unittest.cc. class WebRtcVideoFrameTest : public VideoFrameTest<cricket::WebRtcVideoFrame> { public: WebRtcVideoFrameTest() { - repeat_ = FLAG_yuvconverter_repeat; } void TestInit(int cropped_width, int cropped_height) { @@ -136,7 +132,7 @@ TEST_WEBRTCVIDEOFRAME(ConstructI420CropVertical) TEST_WEBRTCVIDEOFRAME(ConstructBlack) // TODO(fbarchard): Implement Jpeg // TEST_WEBRTCVIDEOFRAME(ConstructMjpgI420) -// TEST_WEBRTCVIDEOFRAME(ConstructMjpgI422) +TEST_WEBRTCVIDEOFRAME(ConstructMjpgI422) // TEST_WEBRTCVIDEOFRAME(ConstructMjpgI444) // TEST_WEBRTCVIDEOFRAME(ConstructMjpgI411) // TEST_WEBRTCVIDEOFRAME(ConstructMjpgI400) diff --git a/media/webrtc/webrtcvoiceengine.cc b/media/webrtc/webrtcvoiceengine.cc index 5cf53e4..a524bad 100644 --- a/media/webrtc/webrtcvoiceengine.cc +++ b/media/webrtc/webrtcvoiceengine.cc @@ -52,6 +52,7 @@ #include "webrtc/base/stringutils.h" #include "webrtc/common.h" #include "webrtc/modules/audio_processing/include/audio_processing.h" +#include "webrtc/video_engine/include/vie_network.h" #ifdef WIN32 #include <objbase.h> // NOLINT @@ -119,6 +120,7 @@ static const int kOpusStereoBitrate = 64000; // Opus bitrate should be in the range between 6000 and 510000. static const int kOpusMinBitrate = 6000; static const int kOpusMaxBitrate = 510000; + // Default audio dscp value. // See http://tools.ietf.org/html/rfc2474 for details. // See also http://tools.ietf.org/html/draft-jennings-rtcweb-qos-00 @@ -403,6 +405,7 @@ static bool IsOpusStereoEnabled(const AudioCodec& codec) { return codec.GetParam(kCodecParamStereo, &value) && value == 1; } +// TODO(minyue): Clamp bitrate when invalid. static bool IsValidOpusBitrate(int bitrate) { return (bitrate >= kOpusMinBitrate && bitrate <= kOpusMaxBitrate); } @@ -429,6 +432,59 @@ static bool IsOpusFecEnabled(const AudioCodec& codec) { return codec.GetParam(kCodecParamUseInbandFec, &value) && value == 1; } +// Returns kOpusDefaultPlaybackRate if params[kCodecParamMaxPlaybackRate] is not +// defined. Returns the value of params[kCodecParamMaxPlaybackRate] otherwise. +static int GetOpusMaxPlaybackRate(const AudioCodec& codec) { + int value; + if (codec.GetParam(kCodecParamMaxPlaybackRate, &value)) { + return value; + } + return kOpusDefaultMaxPlaybackRate; +} + +static void GetOpusConfig(const AudioCodec& codec, webrtc::CodecInst* voe_codec, + bool* enable_codec_fec, int* max_playback_rate) { + *enable_codec_fec = IsOpusFecEnabled(codec); + *max_playback_rate = GetOpusMaxPlaybackRate(codec); + + // If OPUS, change what we send according to the "stereo" codec + // parameter, and not the "channels" parameter. We set + // voe_codec.channels to 2 if "stereo=1" and 1 otherwise. If + // the bitrate is not specified, i.e. is zero, we set it to the + // appropriate default value for mono or stereo Opus. + + // TODO(minyue): The determination of bit rate might take the maximum playback + // rate into account. + + if (IsOpusStereoEnabled(codec)) { + voe_codec->channels = 2; + if (!IsValidOpusBitrate(codec.bitrate)) { + if (codec.bitrate != 0) { + LOG(LS_WARNING) << "Overrides the invalid supplied bitrate(" + << codec.bitrate + << ") with default opus stereo bitrate: " + << kOpusStereoBitrate; + } + voe_codec->rate = kOpusStereoBitrate; + } + } else { + voe_codec->channels = 1; + if (!IsValidOpusBitrate(codec.bitrate)) { + if (codec.bitrate != 0) { + LOG(LS_WARNING) << "Overrides the invalid supplied bitrate(" + << codec.bitrate + << ") with default opus mono bitrate: " + << kOpusMonoBitrate; + } + voe_codec->rate = kOpusMonoBitrate; + } + } + int bitrate_from_params = GetOpusBitrateFromParams(codec); + if (bitrate_from_params != 0) { + voe_codec->rate = bitrate_from_params; + } +} + void WebRtcVoiceEngine::ConstructCodecs() { LOG(LS_INFO) << "WebRtc VoiceEngine codecs:"; int ncodecs = voe_wrapper_->codec()->NumOfCodecs(); @@ -805,30 +861,6 @@ bool WebRtcVoiceEngine::ApplyOptions(const AudioOptions& options_in) { } } - bool experimental_ns; - if (options.experimental_ns.Get(&experimental_ns)) { - webrtc::AudioProcessing* audioproc = - voe_wrapper_->base()->audio_processing(); -#ifdef USE_WEBRTC_DEV_BRANCH - webrtc::Config config; - config.Set<webrtc::ExperimentalNs>(new webrtc::ExperimentalNs( - experimental_ns)); - audioproc->SetExtraOptions(config); -#else - // We check audioproc for the benefit of tests, since FakeWebRtcVoiceEngine - // returns NULL on audio_processing(). - if (audioproc) { - if (audioproc->EnableExperimentalNs(experimental_ns) == -1) { - LOG_RTCERR1(EnableExperimentalNs, experimental_ns); - return false; - } - } else { - LOG(LS_VERBOSE) << "Experimental noise suppression set to " - << experimental_ns; - } -#endif - } - bool highpass_filter; if (options.highpass_filter.Get(&highpass_filter)) { LOG(LS_INFO) << "High pass filter enabled? " << highpass_filter; @@ -874,20 +906,50 @@ bool WebRtcVoiceEngine::ApplyOptions(const AudioOptions& options_in) { StopAecDump(); } + webrtc::Config config; + + experimental_aec_.SetFrom(options.experimental_aec); bool experimental_aec; - if (options.experimental_aec.Get(&experimental_aec)) { - LOG(LS_INFO) << "Experimental aec is " << experimental_aec; - webrtc::AudioProcessing* audioproc = - voe_wrapper_->base()->audio_processing(); + if (experimental_aec_.Get(&experimental_aec)) { + LOG(LS_INFO) << "Experimental aec is enabled? " << experimental_aec; + config.Set<webrtc::DelayCorrection>( + new webrtc::DelayCorrection(experimental_aec)); + } + +#ifdef USE_WEBRTC_DEV_BRANCH + experimental_ns_.SetFrom(options.experimental_ns); + bool experimental_ns; + if (experimental_ns_.Get(&experimental_ns)) { + LOG(LS_INFO) << "Experimental ns is enabled? " << experimental_ns; + config.Set<webrtc::ExperimentalNs>( + new webrtc::ExperimentalNs(experimental_ns)); + } +#endif + + // We check audioproc for the benefit of tests, since FakeWebRtcVoiceEngine + // returns NULL on audio_processing(). + webrtc::AudioProcessing* audioproc = voe_wrapper_->base()->audio_processing(); + if (audioproc) { + audioproc->SetExtraOptions(config); + } + +#ifndef USE_WEBRTC_DEV_BRANCH + bool experimental_ns; + if (options.experimental_ns.Get(&experimental_ns)) { + LOG(LS_INFO) << "Experimental ns is enabled? " << experimental_ns; // We check audioproc for the benefit of tests, since FakeWebRtcVoiceEngine // returns NULL on audio_processing(). if (audioproc) { - webrtc::Config config; - config.Set<webrtc::DelayCorrection>( - new webrtc::DelayCorrection(experimental_aec)); - audioproc->SetExtraOptions(config); + if (audioproc->EnableExperimentalNs(experimental_ns) == -1) { + LOG_RTCERR1(EnableExperimentalNs, experimental_ns); + return false; + } + } else { + LOG(LS_VERBOSE) << "Experimental noise suppression set to " + << experimental_ns; } } +#endif uint32 recording_sample_rate; if (options.recording_sample_rate.Get(&recording_sample_rate)) { @@ -1759,6 +1821,8 @@ WebRtcVoiceMediaChannel::WebRtcVoiceMediaChannel(WebRtcVoiceEngine *engine) typing_noise_detected_(false), desired_send_(SEND_NOTHING), send_(SEND_NOTHING), + shared_bwe_vie_(NULL), + shared_bwe_vie_channel_(-1), default_receive_ssrc_(0) { engine->RegisterChannel(this); LOG(LS_VERBOSE) << "WebRtcVoiceMediaChannel::WebRtcVoiceMediaChannel " @@ -1770,6 +1834,7 @@ WebRtcVoiceMediaChannel::WebRtcVoiceMediaChannel(WebRtcVoiceEngine *engine) WebRtcVoiceMediaChannel::~WebRtcVoiceMediaChannel() { LOG(LS_VERBOSE) << "WebRtcVoiceMediaChannel::~WebRtcVoiceMediaChannel " << voe_channel(); + SetupSharedBandwidthEstimation(NULL, -1); // Remove any remaining send streams, the default channel will be deleted // later. @@ -1870,6 +1935,12 @@ bool WebRtcVoiceMediaChannel::SetOptions(const AudioOptions& options) { } } + // Force update of Video Engine BWE forwarding to reflect experiment setting. + if (!SetupSharedBandwidthEstimation(shared_bwe_vie_, + shared_bwe_vie_channel_)) { + return false; + } + LOG(LS_INFO) << "Set voice channel options. Current options: " << options_.ToString(); return true; @@ -1976,6 +2047,10 @@ bool WebRtcVoiceMediaChannel::SetSendCodecs( bool nack_enabled = nack_enabled_; bool enable_codec_fec = false; + // max_playback_rate <= 0 will not trigger setting of maximum encoding + // bandwidth. + int max_playback_rate = 0; + // Set send codec (the first non-telephone-event/CN codec) for (std::vector<AudioCodec>::const_iterator it = codecs.begin(); it != codecs.end(); ++it) { @@ -1992,40 +2067,6 @@ bool WebRtcVoiceMediaChannel::SetSendCodecs( continue; } - // If OPUS, change what we send according to the "stereo" codec - // parameter, and not the "channels" parameter. We set - // voe_codec.channels to 2 if "stereo=1" and 1 otherwise. If - // the bitrate is not specified, i.e. is zero, we set it to the - // appropriate default value for mono or stereo Opus. - if (IsOpus(*it)) { - if (IsOpusStereoEnabled(*it)) { - voe_codec.channels = 2; - if (!IsValidOpusBitrate(it->bitrate)) { - if (it->bitrate != 0) { - LOG(LS_WARNING) << "Overrides the invalid supplied bitrate(" - << it->bitrate - << ") with default opus stereo bitrate: " - << kOpusStereoBitrate; - } - voe_codec.rate = kOpusStereoBitrate; - } - } else { - voe_codec.channels = 1; - if (!IsValidOpusBitrate(it->bitrate)) { - if (it->bitrate != 0) { - LOG(LS_WARNING) << "Overrides the invalid supplied bitrate(" - << it->bitrate - << ") with default opus mono bitrate: " - << kOpusMonoBitrate; - } - voe_codec.rate = kOpusMonoBitrate; - } - } - int bitrate_from_params = GetOpusBitrateFromParams(*it); - if (bitrate_from_params != 0) { - voe_codec.rate = bitrate_from_params; - } - } // We'll use the first codec in the list to actually send audio data. // Be sure to use the payload type requested by the remote side. @@ -2055,8 +2096,11 @@ bool WebRtcVoiceMediaChannel::SetSendCodecs( } else { send_codec = voe_codec; nack_enabled = IsNackEnabled(*it); - // For Opus as the send codec, we enable inband FEC if requested. - enable_codec_fec = IsOpus(*it) && IsOpusFecEnabled(*it); + // For Opus as the send codec, we are to enable inband FEC if requested + // and set maximum playback rate. + if (IsOpus(*it)) { + GetOpusConfig(*it, &send_codec, &enable_codec_fec, &max_playback_rate); + } } found_send_codec = true; break; @@ -2090,6 +2134,21 @@ bool WebRtcVoiceMediaChannel::SetSendCodecs( #endif // USE_WEBRTC_DEV_BRANCH } + // maxplaybackrate should be set after SetSendCodec. + if (max_playback_rate > 0) { + LOG(LS_INFO) << "Attempt to set maximum playback rate to " + << max_playback_rate + << " Hz on channel " + << channel; +#ifdef USE_WEBRTC_DEV_BRANCH + // (max_playback_rate + 1) >> 1 is to obtain ceil(max_playback_rate / 2.0). + if (engine()->voe()->codec()->SetOpusMaxPlaybackRate( + channel, max_playback_rate) == -1) { + LOG(LS_WARNING) << "Could not set maximum playback rate."; + } +#endif + } + // Always update the |send_codec_| to the currently set send codec. send_codec_.reset(new webrtc::CodecInst(send_codec)); @@ -2531,8 +2590,8 @@ bool WebRtcVoiceMediaChannel::AddSendStream(const StreamParams& sp) { } if (engine()->voe()->rtp()->SetRTCP_CNAME(channel, sp.cname.c_str()) == -1) { - LOG_RTCERR2(SetRTCP_CNAME, channel, sp.cname); - return false; + LOG_RTCERR2(SetRTCP_CNAME, channel, sp.cname); + return false; } // Set the current codecs to be used for the new channel. @@ -2604,6 +2663,9 @@ bool WebRtcVoiceMediaChannel::AddRecvStream(const StreamParams& sp) { receive_channels_.insert(std::make_pair( default_receive_ssrc_, new WebRtcVoiceChannelRenderer(voe_channel(), audio_transport))); + if (!SetupSharedBweOnChannel(voe_channel())) { + return false; + } return SetPlayout(voe_channel(), playout_); } @@ -2691,6 +2753,11 @@ bool WebRtcVoiceMediaChannel::ConfigureRecvChannel(int channel) { return false; } + // Set up channel to be able to forward incoming packets to video engine BWE. + if (!SetupSharedBweOnChannel(channel)) { + return false; + } + return SetPlayout(channel, playout_); } @@ -3060,7 +3127,8 @@ void WebRtcVoiceMediaChannel::OnPacketReceived( engine()->voe()->network()->ReceivedRTPPacket( which_channel, packet->data(), - static_cast<unsigned int>(packet->length())); + static_cast<unsigned int>(packet->length()), + webrtc::PacketTime(packet_time.timestamp, packet_time.not_before)); } void WebRtcVoiceMediaChannel::OnRtcpReceived( @@ -3454,6 +3522,23 @@ int WebRtcVoiceMediaChannel::GetSendChannelNum(uint32 ssrc) { return -1; } +bool WebRtcVoiceMediaChannel::SetupSharedBandwidthEstimation( + webrtc::VideoEngine* vie, int vie_channel) { + shared_bwe_vie_ = vie; + shared_bwe_vie_channel_ = vie_channel; + + if (!SetupSharedBweOnChannel(voe_channel())) { + return false; + } + for (ChannelMap::iterator it = receive_channels_.begin(); + it != receive_channels_.end(); ++it) { + if (!SetupSharedBweOnChannel(it->second->channel())) { + return false; + } + } + return true; +} + bool WebRtcVoiceMediaChannel::GetRedSendCodec(const AudioCodec& red_codec, const std::vector<AudioCodec>& all_codecs, webrtc::CodecInst* send_codec) { // Get the RED encodings from the parameter with no name. This may @@ -3603,6 +3688,25 @@ bool WebRtcVoiceMediaChannel::SetHeaderExtension(ExtensionSetterFunction setter, return true; } +bool WebRtcVoiceMediaChannel::SetupSharedBweOnChannel(int voe_channel) { + webrtc::ViENetwork* vie_network = NULL; + int vie_channel = -1; + if (options_.combined_audio_video_bwe.GetWithDefaultIfUnset(false) && + shared_bwe_vie_ != NULL && shared_bwe_vie_channel_ != -1) { + vie_network = webrtc::ViENetwork::GetInterface(shared_bwe_vie_); + vie_channel = shared_bwe_vie_channel_; + } + if (engine()->voe()->rtp()->SetVideoEngineBWETarget(voe_channel, vie_network, + vie_channel) == -1) { + LOG_RTCERR3(SetVideoEngineBWETarget, voe_channel, vie_network, vie_channel); + if (vie_network != NULL) { + // Don't fail if we're tearing down. + return false; + } + } + return true; +} + int WebRtcSoundclipStream::Read(void *buf, int len) { size_t res = 0; mem_.Read(buf, len, &res, NULL); diff --git a/media/webrtc/webrtcvoiceengine.h b/media/webrtc/webrtcvoiceengine.h index 69705cc..5557af0 100644 --- a/media/webrtc/webrtcvoiceengine.h +++ b/media/webrtc/webrtcvoiceengine.h @@ -54,6 +54,10 @@ #error "Bogus include." #endif +namespace webrtc { +class VideoEngine; +} + namespace cricket { // WebRtcSoundclipStream is an adapter object that allows a memory stream to be @@ -280,6 +284,13 @@ class WebRtcVoiceEngine uint32 rx_processor_ssrc_; rtc::CriticalSection signal_media_critical_; + + // Cache received experimental_aec and experimental_ns values, and apply them + // in case they are missing in the audio options. We need to do this because + // SetExtraOptions() will revert to defaults for options which are not + // provided. + Settable<bool> experimental_aec_; + Settable<bool> experimental_ns_; }; // WebRtcMediaChannel is a class that implements the common WebRtc channel @@ -377,6 +388,8 @@ class WebRtcVoiceMediaChannel int GetReceiveChannelNum(uint32 ssrc); int GetSendChannelNum(uint32 ssrc); + bool SetupSharedBandwidthEstimation(webrtc::VideoEngine* vie, + int vie_channel); protected: int GetLastEngineError() { return engine()->GetLastEngineError(); } int GetOutputLevel(int channel); @@ -419,6 +432,7 @@ class WebRtcVoiceMediaChannel bool SetHeaderExtension(ExtensionSetterFunction setter, int channel_id, const RtpHeaderExtension* extension); + bool SetupSharedBweOnChannel(int voe_channel); bool SetChannelRecvRtpHeaderExtensions( int channel_id, @@ -442,6 +456,11 @@ class WebRtcVoiceMediaChannel bool typing_noise_detected_; SendFlags desired_send_; SendFlags send_; + // shared_bwe_vie_ and shared_bwe_vie_channel_ together identifies a WebRTC + // VideoEngine channel that this voice channel should forward incoming packets + // to for Bandwidth Estimation purposes. + webrtc::VideoEngine* shared_bwe_vie_; + int shared_bwe_vie_channel_; // send_channels_ contains the channels which are being used for sending. // When the default channel (voe_channel) is used for sending, it is diff --git a/media/webrtc/webrtcvoiceengine_unittest.cc b/media/webrtc/webrtcvoiceengine_unittest.cc index 89d4c4d..b044e92 100644 --- a/media/webrtc/webrtcvoiceengine_unittest.cc +++ b/media/webrtc/webrtcvoiceengine_unittest.cc @@ -38,6 +38,7 @@ #include "talk/media/base/fakenetworkinterface.h" #include "talk/media/base/fakertp.h" #include "talk/media/webrtc/fakewebrtcvoiceengine.h" +#include "talk/media/webrtc/webrtcvie.h" #include "talk/media/webrtc/webrtcvoiceengine.h" #include "talk/p2p/base/fakesession.h" #include "talk/session/media/channel.h" @@ -1151,7 +1152,6 @@ TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecNoOpusFec) { int channel_num = voe_.GetLastChannel(); std::vector<cricket::AudioCodec> codecs; codecs.push_back(kOpusCodec); - codecs[0].bitrate = 0; EXPECT_TRUE(channel_->SetSendCodecs(codecs)); EXPECT_FALSE(voe_.GetCodecFEC(channel_num)); } @@ -1228,6 +1228,159 @@ TEST_F(WebRtcVoiceEngineTestFake, SetSendCodecIsacWithParamNoFec) { EXPECT_TRUE(channel_->SetSendCodecs(codecs)); EXPECT_FALSE(voe_.GetCodecFEC(channel_num)); } + +// Test that Opus FEC status can be changed. +TEST_F(WebRtcVoiceEngineTestFake, ChangeOpusFecStatus) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector<cricket::AudioCodec> codecs; + codecs.push_back(kOpusCodec); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_FALSE(voe_.GetCodecFEC(channel_num)); + codecs[0].params["useinbandfec"] = "1"; + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_TRUE(voe_.GetCodecFEC(channel_num)); +} + +// Test maxplaybackrate <= 8000 triggers Opus narrow band mode. +TEST_F(WebRtcVoiceEngineTestFake, SetOpusMaxPlaybackRateNb) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector<cricket::AudioCodec> codecs; + codecs.push_back(kOpusCodec); + codecs[0].bitrate = 0; + codecs[0].SetParam(cricket::kCodecParamMaxPlaybackRate, 8000); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_EQ(cricket::kOpusBandwidthNb, + voe_.GetMaxEncodingBandwidth(channel_num)); + webrtc::CodecInst gcodec; + EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec)); + EXPECT_STREQ("opus", gcodec.plname); + // TODO(minyue): Default bit rate is not but can in future be affected by + // kCodecParamMaxPlaybackRate. + EXPECT_EQ(32000, gcodec.rate); +} + +// Test 8000 < maxplaybackrate <= 12000 triggers Opus medium band mode. +TEST_F(WebRtcVoiceEngineTestFake, SetOpusMaxPlaybackRateMb) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector<cricket::AudioCodec> codecs; + codecs.push_back(kOpusCodec); + codecs[0].bitrate = 0; + codecs[0].SetParam(cricket::kCodecParamMaxPlaybackRate, 8001); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_EQ(cricket::kOpusBandwidthMb, + voe_.GetMaxEncodingBandwidth(channel_num)); + webrtc::CodecInst gcodec; + EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec)); + EXPECT_STREQ("opus", gcodec.plname); + // TODO(minyue): Default bit rate is not but can in future be affected by + // kCodecParamMaxPlaybackRate. + EXPECT_EQ(32000, gcodec.rate); +} + +// Test 12000 < maxplaybackrate <= 16000 triggers Opus wide band mode. +TEST_F(WebRtcVoiceEngineTestFake, SetOpusMaxPlaybackRateWb) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector<cricket::AudioCodec> codecs; + codecs.push_back(kOpusCodec); + codecs[0].bitrate = 0; + codecs[0].SetParam(cricket::kCodecParamMaxPlaybackRate, 12001); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_EQ(cricket::kOpusBandwidthWb, + voe_.GetMaxEncodingBandwidth(channel_num)); + webrtc::CodecInst gcodec; + EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec)); + EXPECT_STREQ("opus", gcodec.plname); + // TODO(minyue): Default bit rate is not but can in future be affected by + // kCodecParamMaxPlaybackRate. + EXPECT_EQ(32000, gcodec.rate); +} + +// Test 16000 < maxplaybackrate <= 24000 triggers Opus super wide band mode. +TEST_F(WebRtcVoiceEngineTestFake, SetOpusMaxPlaybackRateSwb) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector<cricket::AudioCodec> codecs; + codecs.push_back(kOpusCodec); + codecs[0].bitrate = 0; + codecs[0].SetParam(cricket::kCodecParamMaxPlaybackRate, 16001); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_EQ(cricket::kOpusBandwidthSwb, + voe_.GetMaxEncodingBandwidth(channel_num)); + webrtc::CodecInst gcodec; + EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec)); + EXPECT_STREQ("opus", gcodec.plname); + // TODO(minyue): Default bit rate is not but can in future be affected by + // kCodecParamMaxPlaybackRate. + EXPECT_EQ(32000, gcodec.rate); +} + +// Test 24000 < maxplaybackrate triggers Opus full band mode. +TEST_F(WebRtcVoiceEngineTestFake, SetOpusMaxPlaybackRateFb) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector<cricket::AudioCodec> codecs; + codecs.push_back(kOpusCodec); + codecs[0].bitrate = 0; + codecs[0].SetParam(cricket::kCodecParamMaxPlaybackRate, 24001); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_EQ(cricket::kOpusBandwidthFb, + voe_.GetMaxEncodingBandwidth(channel_num)); + webrtc::CodecInst gcodec; + EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec)); + EXPECT_STREQ("opus", gcodec.plname); + // TODO(minyue): Default bit rate is not but can in future be affected by + // kCodecParamMaxPlaybackRate. + EXPECT_EQ(32000, gcodec.rate); +} + +// Test Opus that without maxplaybackrate, default playback rate is used. +TEST_F(WebRtcVoiceEngineTestFake, DefaultOpusMaxPlaybackRate) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector<cricket::AudioCodec> codecs; + codecs.push_back(kOpusCodec); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_EQ(cricket::kOpusBandwidthFb, + voe_.GetMaxEncodingBandwidth(channel_num)); +} + +// Test the with non-Opus, maxplaybackrate has no effect. +TEST_F(WebRtcVoiceEngineTestFake, SetNonOpusMaxPlaybackRate) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector<cricket::AudioCodec> codecs; + codecs.push_back(kIsacCodec); + codecs[0].SetParam(cricket::kCodecParamMaxPlaybackRate, 32000); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_EQ(0, voe_.GetMaxEncodingBandwidth(channel_num)); +} + +// Test maxplaybackrate can be set on two streams. +TEST_F(WebRtcVoiceEngineTestFake, SetOpusMaxPlaybackRateOnTwoStreams) { + EXPECT_TRUE(SetupEngine()); + int channel_num = voe_.GetLastChannel(); + std::vector<cricket::AudioCodec> codecs; + codecs.push_back(kOpusCodec); + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + // Default bandwidth is 24000. + EXPECT_EQ(cricket::kOpusBandwidthFb, + voe_.GetMaxEncodingBandwidth(channel_num)); + + codecs[0].SetParam(cricket::kCodecParamMaxPlaybackRate, 8000); + + EXPECT_TRUE(channel_->SetSendCodecs(codecs)); + EXPECT_EQ(cricket::kOpusBandwidthNb, + voe_.GetMaxEncodingBandwidth(channel_num)); + + channel_->AddSendStream(cricket::StreamParams::CreateLegacy(kSsrc2)); + channel_num = voe_.GetLastChannel(); + EXPECT_EQ(cricket::kOpusBandwidthNb, + voe_.GetMaxEncodingBandwidth(channel_num)); +} #endif // USE_WEBRTC_DEV_BRANCH // Test that we can apply CELT with stereo mode but fail with mono mode. @@ -3160,3 +3313,113 @@ TEST(WebRtcVoiceEngineTest, CoInitialize) { CoUninitialize(); } #endif + +TEST_F(WebRtcVoiceEngineTestFake, ChangeCombinedAudioVideoBweOption) { + // Test that changing the combined_audio_video_bwe option results in the + // expected state changes in VoiceEngine. + cricket::ViEWrapper vie; + const int kVieCh = 667; + + EXPECT_TRUE(SetupEngine()); + cricket::WebRtcVoiceMediaChannel* media_channel = + static_cast<cricket::WebRtcVoiceMediaChannel*>(channel_); + EXPECT_TRUE(media_channel->SetupSharedBandwidthEstimation(vie.engine(), + kVieCh)); + EXPECT_TRUE(media_channel->AddRecvStream( + cricket::StreamParams::CreateLegacy(2))); + int recv_ch = voe_.GetLastChannel(); + + // Combined BWE should not be set up yet. + EXPECT_EQ(NULL, voe_.GetViENetwork(recv_ch)); + EXPECT_EQ(-1, voe_.GetVideoChannel(recv_ch)); + + // Enable combined BWE option - now it should be set up. + cricket::AudioOptions options; + options.combined_audio_video_bwe.Set(true); + EXPECT_TRUE(media_channel->SetOptions(options)); + EXPECT_EQ(vie.network(), voe_.GetViENetwork(recv_ch)); + EXPECT_EQ(kVieCh, voe_.GetVideoChannel(recv_ch)); + + // Disable combined BWE option - should be disabled again. + options.combined_audio_video_bwe.Set(false); + EXPECT_TRUE(media_channel->SetOptions(options)); + EXPECT_EQ(NULL, voe_.GetViENetwork(recv_ch)); + EXPECT_EQ(-1, voe_.GetVideoChannel(recv_ch)); + + EXPECT_TRUE(media_channel->SetupSharedBandwidthEstimation(NULL, -1)); +} + +TEST_F(WebRtcVoiceEngineTestFake, SetupSharedBandwidthEstimation) { + // Test that calling SetupSharedBandwidthEstimation() on the voice media + // channel results in the expected state changes in VoiceEngine. + cricket::ViEWrapper vie1; + cricket::ViEWrapper vie2; + const int kVieCh1 = 667; + const int kVieCh2 = 70; + + EXPECT_TRUE(SetupEngine()); + cricket::WebRtcVoiceMediaChannel* media_channel = + static_cast<cricket::WebRtcVoiceMediaChannel*>(channel_); + cricket::AudioOptions options; + options.combined_audio_video_bwe.Set(true); + EXPECT_TRUE(media_channel->SetOptions(options)); + EXPECT_TRUE(media_channel->AddRecvStream( + cricket::StreamParams::CreateLegacy(2))); + int recv_ch = voe_.GetLastChannel(); + + // Combined BWE should not be set up yet. + EXPECT_EQ(NULL, voe_.GetViENetwork(recv_ch)); + EXPECT_EQ(-1, voe_.GetVideoChannel(recv_ch)); + + // Register - should be enabled. + EXPECT_TRUE(media_channel->SetupSharedBandwidthEstimation(vie1.engine(), + kVieCh1)); + EXPECT_EQ(vie1.network(), voe_.GetViENetwork(recv_ch)); + EXPECT_EQ(kVieCh1, voe_.GetVideoChannel(recv_ch)); + + // Re-register - should still be enabled. + EXPECT_TRUE(media_channel->SetupSharedBandwidthEstimation(vie2.engine(), + kVieCh2)); + EXPECT_EQ(vie2.network(), voe_.GetViENetwork(recv_ch)); + EXPECT_EQ(kVieCh2, voe_.GetVideoChannel(recv_ch)); + + // Unregister - should be disabled again. + EXPECT_TRUE(media_channel->SetupSharedBandwidthEstimation(NULL, -1)); + EXPECT_EQ(NULL, voe_.GetViENetwork(recv_ch)); + EXPECT_EQ(-1, voe_.GetVideoChannel(recv_ch)); +} + +TEST_F(WebRtcVoiceEngineTestFake, ConfigureCombinedBweForNewRecvStreams) { + // Test that adding receive streams after enabling combined bandwidth + // estimation will correctly configure each channel. + cricket::ViEWrapper vie; + const int kVieCh = 667; + + EXPECT_TRUE(SetupEngine()); + cricket::WebRtcVoiceMediaChannel* media_channel = + static_cast<cricket::WebRtcVoiceMediaChannel*>(channel_); + EXPECT_TRUE(media_channel->SetupSharedBandwidthEstimation(vie.engine(), + kVieCh)); + cricket::AudioOptions options; + options.combined_audio_video_bwe.Set(true); + EXPECT_TRUE(media_channel->SetOptions(options)); + + static const uint32 kSsrcs[] = {1, 2, 3, 4}; + int voe_channels[ARRAY_SIZE(kSsrcs)] = {0}; + for (unsigned int i = 0; i < ARRAY_SIZE(kSsrcs); ++i) { + EXPECT_TRUE(media_channel->AddRecvStream( + cricket::StreamParams::CreateLegacy(kSsrcs[i]))); + int recv_ch = media_channel->GetReceiveChannelNum(kSsrcs[i]); + EXPECT_NE(-1, recv_ch); + voe_channels[i] = recv_ch; + EXPECT_EQ(vie.network(), voe_.GetViENetwork(recv_ch)); + EXPECT_EQ(kVieCh, voe_.GetVideoChannel(recv_ch)); + } + + EXPECT_TRUE(media_channel->SetupSharedBandwidthEstimation(NULL, -1)); + + for (unsigned int i = 0; i < ARRAY_SIZE(voe_channels); ++i) { + EXPECT_EQ(NULL, voe_.GetViENetwork(voe_channels[i])); + EXPECT_EQ(-1, voe_.GetVideoChannel(voe_channels[i])); + } +} |