summaryrefslogtreecommitdiff
path: root/media
diff options
context:
space:
mode:
Diffstat (limited to 'media')
-rw-r--r--media/base/constants.cc2
-rw-r--r--media/base/constants.h3
-rw-r--r--media/base/executablehelpers.h100
-rw-r--r--media/base/fakemediaengine.h8
-rw-r--r--media/base/filemediaengine.h2
-rw-r--r--media/base/filemediaengine_unittest.cc3
-rw-r--r--media/base/hybridvideoengine.cc356
-rw-r--r--media/base/hybridvideoengine.h286
-rw-r--r--media/base/hybridvideoengine_unittest.cc486
-rw-r--r--media/base/mediachannel.h7
-rw-r--r--media/base/mediaengine.h11
-rw-r--r--media/base/testutils.cc10
-rwxr-xr-xmedia/base/videoadapter_unittest.cc205
-rw-r--r--media/base/videoengine_unittest.h19
-rw-r--r--media/base/videoframe.cc57
-rw-r--r--media/base/videoframe_unittest.h51
-rw-r--r--media/devices/linuxdevicemanager.cc16
-rw-r--r--media/devices/linuxdevicemanager.h4
-rw-r--r--media/devices/macdevicemanager.cc4
-rw-r--r--media/devices/macdevicemanagermm.mm53
-rw-r--r--media/other/linphonemediaengine.h2
-rw-r--r--media/sctp/sctpdataengine_unittest.cc18
-rw-r--r--media/testdata/faces.1280x720_P420.yuv1
-rw-r--r--media/testdata/faces_I400.jpgbin0 -> 180612 bytes
-rw-r--r--media/testdata/faces_I411.jpgbin0 -> 180415 bytes
-rw-r--r--media/testdata/faces_I420.jpgbin0 -> 185661 bytes
-rw-r--r--media/testdata/faces_I422.jpgbin0 -> 182044 bytes
-rw-r--r--media/testdata/faces_I444.jpgbin0 -> 185840 bytes
-rw-r--r--media/webrtc/OWNERS2
-rw-r--r--media/webrtc/fakewebrtcvideocapturemodule.h46
-rw-r--r--media/webrtc/fakewebrtcvideoengine.h1
-rw-r--r--media/webrtc/fakewebrtcvoiceengine.h47
-rw-r--r--media/webrtc/webrtcmediaengine.cc5
-rw-r--r--media/webrtc/webrtcmediaengine.h6
-rw-r--r--media/webrtc/webrtcpassthroughrender.h73
-rw-r--r--media/webrtc/webrtcvideoengine.cc348
-rw-r--r--media/webrtc/webrtcvideoengine.h58
-rw-r--r--media/webrtc/webrtcvideoengine2.cc236
-rw-r--r--media/webrtc/webrtcvideoengine2.h60
-rw-r--r--media/webrtc/webrtcvideoengine2_unittest.cc68
-rw-r--r--media/webrtc/webrtcvideoengine2_unittest.h16
-rw-r--r--media/webrtc/webrtcvideoengine_unittest.cc22
-rw-r--r--media/webrtc/webrtcvideoframe_unittest.cc6
-rw-r--r--media/webrtc/webrtcvoiceengine.cc246
-rw-r--r--media/webrtc/webrtcvoiceengine.h19
-rw-r--r--media/webrtc/webrtcvoiceengine_unittest.cc265
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
new file mode 100644
index 0000000..c928f00
--- /dev/null
+++ b/media/testdata/faces_I400.jpg
Binary files differ
diff --git a/media/testdata/faces_I411.jpg b/media/testdata/faces_I411.jpg
new file mode 100644
index 0000000..600c122
--- /dev/null
+++ b/media/testdata/faces_I411.jpg
Binary files differ
diff --git a/media/testdata/faces_I420.jpg b/media/testdata/faces_I420.jpg
new file mode 100644
index 0000000..10c5332
--- /dev/null
+++ b/media/testdata/faces_I420.jpg
Binary files differ
diff --git a/media/testdata/faces_I422.jpg b/media/testdata/faces_I422.jpg
new file mode 100644
index 0000000..9907aa1
--- /dev/null
+++ b/media/testdata/faces_I422.jpg
Binary files differ
diff --git a/media/testdata/faces_I444.jpg b/media/testdata/faces_I444.jpg
new file mode 100644
index 0000000..3422755
--- /dev/null
+++ b/media/testdata/faces_I444.jpg
Binary files differ
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]));
+ }
+}