diff --git a/talk/libjingle.gyp b/talk/libjingle.gyp index 8ad4c110d..f14854cfb 100755 --- a/talk/libjingle.gyp +++ b/talk/libjingle.gyp @@ -489,8 +489,6 @@ 'media/base/mediacommon.h', 'media/base/mediaengine.cc', 'media/base/mediaengine.h', - 'media/base/mutedvideocapturer.cc', - 'media/base/mutedvideocapturer.h', 'media/base/rtpdataengine.cc', 'media/base/rtpdataengine.h', 'media/base/rtpdump.cc', @@ -508,6 +506,7 @@ 'media/base/videocommon.h', 'media/base/videoframe.cc', 'media/base/videoframe.h', + 'media/base/videoframefactory.h', 'media/base/videoprocessor.h', 'media/base/videorenderer.h', 'media/base/voiceprocessor.h', @@ -543,6 +542,8 @@ 'media/webrtc/webrtcvideoengine2.h', 'media/webrtc/webrtcvideoframe.cc', 'media/webrtc/webrtcvideoframe.h', + 'media/webrtc/webrtcvideoframefactory.cc', + 'media/webrtc/webrtcvideoframefactory.h', 'media/webrtc/webrtcvie.h', 'media/webrtc/webrtcvoe.h', 'media/webrtc/webrtcvoiceengine.cc', diff --git a/talk/libjingle_tests.gyp b/talk/libjingle_tests.gyp index 9a00fed70..76f22eaca 100755 --- a/talk/libjingle_tests.gyp +++ b/talk/libjingle_tests.gyp @@ -148,6 +148,7 @@ 'media/base/streamparams_unittest.cc', 'media/base/testutils.cc', 'media/base/testutils.h', + 'media/base/videoadapter_unittest.cc', 'media/base/videocapturer_unittest.cc', 'media/base/videocommon_unittest.cc', 'media/base/videoengine_unittest.h', diff --git a/talk/media/base/fakevideocapturer.h b/talk/media/base/fakevideocapturer.h index 089151fc2..b61ac5dd7 100644 --- a/talk/media/base/fakevideocapturer.h +++ b/talk/media/base/fakevideocapturer.h @@ -36,6 +36,9 @@ #include "talk/media/base/videocapturer.h" #include "talk/media/base/videocommon.h" #include "talk/media/base/videoframe.h" +#ifdef HAVE_WEBRTC_VIDEO +#include "talk/media/webrtc/webrtcvideoframefactory.h" +#endif namespace cricket { @@ -47,6 +50,9 @@ class FakeVideoCapturer : public cricket::VideoCapturer { initial_unix_timestamp_(time(NULL) * rtc::kNumNanosecsPerSec), next_timestamp_(rtc::kNumNanosecsPerMillisec), is_screencast_(false) { +#ifdef HAVE_WEBRTC_VIDEO + set_frame_factory(new cricket::WebRtcVideoFrameFactory()); +#endif // Default supported formats. Use ResetSupportedFormats to over write. std::vector formats; formats.push_back(cricket::VideoFormat(1280, 720, diff --git a/talk/media/base/mutedvideocapturer.cc b/talk/media/base/mutedvideocapturer.cc index 6ff60a061..f122c30f4 100644 --- a/talk/media/base/mutedvideocapturer.cc +++ b/talk/media/base/mutedvideocapturer.cc @@ -1,135 +1,2 @@ -/* - * libjingle - * Copyright 2012 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 "webrtc/base/logging.h" -#include "webrtc/base/thread.h" -#include "talk/media/base/mutedvideocapturer.h" -#include "talk/media/base/videoframe.h" - -#if defined(HAVE_WEBRTC_VIDEO) -#include "talk/media/webrtc/webrtcvideoframe.h" -#endif // HAVE_WEBRTC_VIDEO - - -namespace cricket { - -const char MutedVideoCapturer::kCapturerId[] = "muted_camera"; - -class MutedFramesGenerator : public rtc::MessageHandler { - public: - explicit MutedFramesGenerator(const VideoFormat& format); - virtual ~MutedFramesGenerator(); - - // Called every |interval| ms. From |format|.interval given in the - // constructor. - sigslot::signal1 SignalFrame; - - protected: - virtual void OnMessage(rtc::Message* message); - - private: - rtc::Thread capture_thread_; - rtc::scoped_ptr muted_frame_; - const VideoFormat format_; - const int interval_; - uint32 create_time_; -}; - -MutedFramesGenerator::MutedFramesGenerator(const VideoFormat& format) - : format_(format), - interval_(static_cast(format.interval / - rtc::kNumNanosecsPerMillisec)), - create_time_(rtc::Time()) { - capture_thread_.Start(); - capture_thread_.PostDelayed(interval_, this); -} - -MutedFramesGenerator::~MutedFramesGenerator() { capture_thread_.Clear(this); } - -void MutedFramesGenerator::OnMessage(rtc::Message* message) { - // Queue a new frame as soon as possible to minimize drift. - capture_thread_.PostDelayed(interval_, this); - if (!muted_frame_) { -#if defined(HAVE_WEBRTC_VIDEO) -#define VIDEO_FRAME_NAME WebRtcVideoFrame -#endif -#if defined(VIDEO_FRAME_NAME) - muted_frame_.reset(new VIDEO_FRAME_NAME()); -#else - return; -#endif - } - uint32 current_timestamp = rtc::Time(); - // Delta between create time and current time will be correct even if there is - // a wraparound since they are unsigned integers. - uint32 elapsed_time = current_timestamp - create_time_; - if (!muted_frame_->InitToBlack(format_.width, format_.height, 1, 1, - elapsed_time, current_timestamp)) { - LOG(LS_ERROR) << "Failed to create a black frame."; - } - SignalFrame(muted_frame_.get()); -} - -MutedVideoCapturer::MutedVideoCapturer() { SetId(kCapturerId); } - -MutedVideoCapturer::~MutedVideoCapturer() { Stop(); } - -bool MutedVideoCapturer::GetBestCaptureFormat(const VideoFormat& desired, - VideoFormat* best_format) { - *best_format = desired; - return true; -} - -CaptureState MutedVideoCapturer::Start(const VideoFormat& capture_format) { - if (frame_generator_.get()) { - return CS_RUNNING; - } - frame_generator_.reset(new MutedFramesGenerator(capture_format)); - frame_generator_->SignalFrame - .connect(this, &MutedVideoCapturer::OnMutedFrame); - SetCaptureFormat(&capture_format); - return CS_RUNNING; -} - -void MutedVideoCapturer::Stop() { - frame_generator_.reset(); - SetCaptureFormat(NULL); -} - -bool MutedVideoCapturer::IsRunning() { return frame_generator_.get() != NULL; } - -bool MutedVideoCapturer::GetPreferredFourccs(std::vector* fourccs) { - fourccs->clear(); - fourccs->push_back(cricket::FOURCC_I420); - return true; -} - -void MutedVideoCapturer::OnMutedFrame(VideoFrame* muted_frame) { - SignalVideoFrame(this, muted_frame); -} - -} // namespace cricket +// TODO(pthatcher): Delete this file. Pulse won't work without it for +// some reason. diff --git a/talk/media/base/mutedvideocapturer_unittest.cc b/talk/media/base/mutedvideocapturer_unittest.cc deleted file mode 100755 index 739874fd9..000000000 --- a/talk/media/base/mutedvideocapturer_unittest.cc +++ /dev/null @@ -1,96 +0,0 @@ -/* - * libjingle - * Copyright 2012 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/mutedvideocapturer.h" - -#include "webrtc/base/gunit.h" -#include "talk/media/base/videoframe.h" - -class MutedVideoCapturerTest : public sigslot::has_slots<>, - public testing::Test { - protected: - void SetUp() { - frames_received_ = 0; - capturer_.SignalVideoFrame - .connect(this, &MutedVideoCapturerTest::OnVideoFrame); - } - void OnVideoFrame(cricket::VideoCapturer* capturer, - const cricket::VideoFrame* muted_frame) { - EXPECT_EQ(capturer, &capturer_); - ++frames_received_; - received_width_ = muted_frame->GetWidth(); - received_height_ = muted_frame->GetHeight(); - } - int frames_received() { return frames_received_; } - bool ReceivedCorrectFormat() { - return (received_width_ == capturer_.GetCaptureFormat()->width) && - (received_height_ == capturer_.GetCaptureFormat()->height); - } - - cricket::MutedVideoCapturer capturer_; - int frames_received_; - cricket::VideoFormat capture_format_; - int received_width_; - int received_height_; -}; - -TEST_F(MutedVideoCapturerTest, GetBestCaptureFormat) { - cricket::VideoFormat format(640, 360, cricket::VideoFormat::FpsToInterval(30), - cricket::FOURCC_I420); - cricket::VideoFormat best_format; - EXPECT_TRUE(capturer_.GetBestCaptureFormat(format, &best_format)); - EXPECT_EQ(format.width, best_format.width); - EXPECT_EQ(format.height, best_format.height); - EXPECT_EQ(format.interval, best_format.interval); - EXPECT_EQ(format.fourcc, best_format.fourcc); -} - -TEST_F(MutedVideoCapturerTest, IsScreencast) { - EXPECT_FALSE(capturer_.IsScreencast()); -} - -TEST_F(MutedVideoCapturerTest, GetPreferredFourccs) { - std::vector fourccs; - EXPECT_TRUE(capturer_.GetPreferredFourccs(&fourccs)); - EXPECT_EQ(fourccs.size(), 1u); - EXPECT_TRUE(capturer_.GetPreferredFourccs(&fourccs)); - EXPECT_EQ(fourccs.size(), 1u); - EXPECT_EQ(fourccs[0], cricket::FOURCC_I420); -} - -TEST_F(MutedVideoCapturerTest, Capturing) { - cricket::VideoFormat format(640, 360, cricket::VideoFormat::FpsToInterval(30), - cricket::FOURCC_I420); - EXPECT_EQ(capturer_.Start(format), cricket::CS_RUNNING); - EXPECT_EQ(capturer_.Start(format), cricket::CS_RUNNING); - EXPECT_TRUE(capturer_.IsRunning()); - // 100 ms should be enough to receive 3 frames at FPS of 30. - EXPECT_EQ_WAIT(frames_received(), 1, 100); - EXPECT_TRUE(ReceivedCorrectFormat()); - capturer_.Stop(); - EXPECT_FALSE(capturer_.IsRunning()); -} diff --git a/talk/media/base/videoadapter_unittest.cc b/talk/media/base/videoadapter_unittest.cc new file mode 100755 index 000000000..df4125d8c --- /dev/null +++ b/talk/media/base/videoadapter_unittest.cc @@ -0,0 +1,1285 @@ +/* + * libjingle + * Copyright 2010 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. + */ + +// If we don't have a WebRtcVideoFrame, just skip all of these tests. +#if defined(HAVE_WEBRTC_VIDEO) +#include // For INT_MAX +#include +#include + +#include "webrtc/base/gunit.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/sigslot.h" +#include "talk/media/base/mediachannel.h" +#include "talk/media/base/testutils.h" +#include "talk/media/base/videoadapter.h" +#include "talk/media/devices/filevideocapturer.h" +#include "talk/media/webrtc/webrtcvideoframe.h" + +namespace cricket { + +namespace { + static const uint32 kWaitTimeout = 3000U; // 3 seconds. + static const uint32 kShortWaitTimeout = 1000U; // 1 second. + void UpdateCpuLoad(CoordinatedVideoAdapter* adapter, + int current_cpus, int max_cpus, float process_load, float system_load) { + adapter->set_cpu_load_min_samples(1); + adapter->OnCpuLoadUpdated(current_cpus, max_cpus, + process_load, system_load); + } +} + +class VideoAdapterTest : public testing::Test { + public: + virtual void SetUp() { + capturer_.reset(new FileVideoCapturer); + EXPECT_TRUE(capturer_->Init(GetTestFilePath( + "captured-320x240-2s-48.frames"))); + capture_format_ = capturer_->GetSupportedFormats()->at(0); + capture_format_.interval = VideoFormat::FpsToInterval(50); + adapter_.reset(new VideoAdapter()); + adapter_->SetInputFormat(capture_format_); + + listener_.reset(new VideoCapturerListener(adapter_.get())); + capturer_->SignalFrameCaptured.connect( + listener_.get(), &VideoCapturerListener::OnFrameCaptured); + } + + void VerifyAdaptedResolution(int width, int height) { + EXPECT_TRUE(NULL != listener_->adapted_frame()); + EXPECT_EQ(static_cast(width), + listener_->adapted_frame()->GetWidth()); + EXPECT_EQ(static_cast(height), + listener_->adapted_frame()->GetHeight()); + } + + protected: + class VideoCapturerListener: public sigslot::has_slots<> { + public: + explicit VideoCapturerListener(VideoAdapter* adapter) + : video_adapter_(adapter), + adapted_frame_(NULL), + copied_output_frame_(), + captured_frames_(0), + dropped_frames_(0), + last_adapt_was_no_op_(false) { + } + + void OnFrameCaptured(VideoCapturer* capturer, + const CapturedFrame* captured_frame) { + WebRtcVideoFrame temp_i420; + EXPECT_TRUE(temp_i420.Init(captured_frame, + captured_frame->width, abs(captured_frame->height))); + VideoFrame* out_frame = NULL; + EXPECT_TRUE(video_adapter_->AdaptFrame(&temp_i420, &out_frame)); + if (out_frame) { + if (out_frame == &temp_i420) { + last_adapt_was_no_op_ = true; + copied_output_frame_.reset(temp_i420.Copy()); + adapted_frame_ = copied_output_frame_.get(); + } else { + last_adapt_was_no_op_ = false; + adapted_frame_ = out_frame; + copied_output_frame_.reset(); + } + } else { + ++dropped_frames_; + } + ++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_; } + + private: + VideoAdapter* video_adapter_; + const VideoFrame* adapted_frame_; + rtc::scoped_ptr copied_output_frame_; + int captured_frames_; + int dropped_frames_; + bool last_adapt_was_no_op_; + }; + + class CpuAdapterListener: public sigslot::has_slots<> { + public: + CpuAdapterListener() : received_cpu_signal_(false) {} + void OnCpuAdaptationSignalled() { received_cpu_signal_ = true; } + bool received_cpu_signal() { return received_cpu_signal_; } + private: + bool received_cpu_signal_; + }; + + rtc::scoped_ptr capturer_; + rtc::scoped_ptr adapter_; + rtc::scoped_ptr listener_; + VideoFormat capture_format_; +}; + + +// Test adapter remembers exact pixel count +TEST_F(VideoAdapterTest, AdaptNumPixels) { + adapter_->SetOutputNumPixels(123456); + EXPECT_EQ(123456, adapter_->GetOutputNumPixels()); +} + +// Test adapter is constructed but not activated. Expect no frame drop and no +// resolution change. +TEST_F(VideoAdapterTest, AdaptInactive) { + // Output resolution is not set. + EXPECT_EQ(INT_MAX, adapter_->GetOutputNumPixels()); + + // Call Adapter with some frames. + EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); + EXPECT_TRUE_WAIT(!capturer_->IsRunning() || + listener_->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); +} + +// Do not adapt the frame rate or the resolution. Expect no frame drop and no +// resolution change. +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); + + // 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()); +} + +TEST_F(VideoAdapterTest, AdaptZeroInterval) { + VideoFormat format = capturer_->GetSupportedFormats()->at(0); + format.interval = 0; + adapter_->SetInputFormat(format); + adapter_->SetOutputFormat(format); + EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); + EXPECT_TRUE_WAIT(!capturer_->IsRunning() || + listener_->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); +} + +// Adapt the frame rate to be half of the capture rate at the beginning. Expect +// the number of dropped frames to be half of the number the captured frames. +TEST_F(VideoAdapterTest, AdaptFramerate) { + VideoFormat request_format = capture_format_; + request_format.interval *= 2; + adapter_->SetOutputFormat(request_format); + EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); + EXPECT_TRUE_WAIT(!capturer_->IsRunning() || + listener_->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); +} + +// Adapt the frame rate to be half of the capture rate at the beginning. Expect +// the number of dropped frames to be half of the number the captured frames. +TEST_F(VideoAdapterTest, AdaptFramerateVariable) { + VideoFormat request_format = capture_format_; + request_format.interval = request_format.interval * 3 / 2; + adapter_->SetOutputFormat(request_format); + EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); + EXPECT_TRUE_WAIT(!capturer_->IsRunning() || + listener_->captured_frames() >= 30, kWaitTimeout); + + // Verify frame drop and no resolution change. + EXPECT_GE(listener_->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); +} + +// Adapt the frame rate to be half of the capture rate after capturing no less +// than 10 frames. Expect no frame dropped before adaptation and frame dropped +// after adaptation. +TEST_F(VideoAdapterTest, AdaptFramerateOntheFly) { + VideoFormat request_format = capture_format_; + adapter_->SetOutputFormat(request_format); + EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); + EXPECT_TRUE_WAIT(!capturer_->IsRunning() || + listener_->captured_frames() >= 10, kWaitTimeout); + + // Verify no frame drop before adaptation. + EXPECT_EQ(0, listener_->dropped_frames()); + + // Adapat the frame rate. + request_format.interval *= 2; + adapter_->SetOutputFormat(request_format); + + EXPECT_TRUE_WAIT(!capturer_->IsRunning() || + listener_->captured_frames() >= 20, kWaitTimeout); + + // Verify frame drop after adaptation. + EXPECT_GT(listener_->dropped_frames(), 0); +} + +// Adapt the frame resolution to be a quarter of the capture resolution at the +// beginning. Expect resolution change. +TEST_F(VideoAdapterTest, AdaptResolution) { + VideoFormat request_format = capture_format_; + request_format.width /= 2; + request_format.height /= 2; + adapter_->SetOutputFormat(request_format); + EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); + EXPECT_TRUE_WAIT(!capturer_->IsRunning() || + listener_->captured_frames() >= 10, kWaitTimeout); + + // Verify no frame drop and resolution change. + EXPECT_EQ(0, listener_->dropped_frames()); + VerifyAdaptedResolution(request_format.width, request_format.height); +} + +// Adapt the frame resolution to half width. Expect resolution change. +TEST_F(VideoAdapterTest, AdaptResolutionNarrow) { + VideoFormat request_format = capture_format_; + request_format.width /= 2; + adapter_->set_scale_third(true); + adapter_->SetOutputFormat(request_format); + EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); + EXPECT_TRUE_WAIT(!capturer_->IsRunning() || + listener_->captured_frames() >= 10, kWaitTimeout); + + // Verify resolution change. + VerifyAdaptedResolution(213, 160); +} + +// Adapt the frame resolution to half height. Expect resolution change. +TEST_F(VideoAdapterTest, AdaptResolutionWide) { + VideoFormat request_format = capture_format_; + request_format.height /= 2; + adapter_->set_scale_third(true); + adapter_->SetOutputFormat(request_format); + EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); + EXPECT_TRUE_WAIT(!capturer_->IsRunning() || + listener_->captured_frames() >= 10, kWaitTimeout); + + // Verify resolution change. + VerifyAdaptedResolution(213, 160); +} + +// Adapt the frame resolution to be a quarter of the capture resolution after +// capturing no less than 10 frames. Expect no resolution change before +// adaptation and resolution change after adaptation. +TEST_F(VideoAdapterTest, AdaptResolutionOnTheFly) { + VideoFormat request_format = capture_format_; + adapter_->SetOutputFormat(request_format); + EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); + EXPECT_TRUE_WAIT(!capturer_->IsRunning() || + listener_->captured_frames() >= 10, kWaitTimeout); + + // Verify no resolution change before adaptation. + VerifyAdaptedResolution(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); + + + // Verify resolution change after adaptation. + VerifyAdaptedResolution(request_format.width, request_format.height); +} + +// Black the output frame. +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); + // 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()); + + adapter_->SetBlackOutput(true); + EXPECT_TRUE_WAIT(!capturer_->IsRunning() || + listener_->captured_frames() >= 20, 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()); + + // 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); + + // Change the output size + VideoFormat request_format = capture_format_; + request_format.width /= 2; + request_format.height /= 2; + adapter_->SetOutputFormat(request_format); + + EXPECT_TRUE_WAIT(!capturer_->IsRunning() || + listener_->captured_frames() >= 40, kWaitTimeout); + // Verify resolution change after adaptation. + VerifyAdaptedResolution(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()); +} + +// Drop all frames. +TEST_F(VideoAdapterTest, DropAllFrames) { + VideoFormat format; // with resolution 0x0. + adapter_->SetOutputFormat(format); + EXPECT_EQ(CS_RUNNING, capturer_->Start(capture_format_)); + EXPECT_TRUE_WAIT(!capturer_->IsRunning() || + listener_->captured_frames() >= 10, kWaitTimeout); + + // Verify all frames are dropped. + EXPECT_GE(listener_->captured_frames(), 10); + EXPECT_EQ(listener_->captured_frames(), listener_->dropped_frames()); +} + +TEST(CoordinatedVideoAdapterTest, TestCoordinatedWithoutCpuAdaptation) { + CoordinatedVideoAdapter adapter; + adapter.set_cpu_adaptation(false); + + VideoFormat format(640, 400, VideoFormat::FpsToInterval(30), FOURCC_I420); + adapter.SetInputFormat(format); + adapter.set_scale_third(true); + EXPECT_EQ(format, adapter.input_format()); + EXPECT_TRUE(adapter.output_format().IsSize0x0()); + + // Server format request 640x400. + format.height = 400; + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(640, adapter.output_format().width); + EXPECT_EQ(400, adapter.output_format().height); + + // Server format request 1280x720, higher than input. Adapt nothing. + format.width = 1280; + format.height = 720; + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(640, adapter.output_format().width); + EXPECT_EQ(400, adapter.output_format().height); + + // Cpu load is high, but cpu adaptation is disabled. Adapt nothing. + adapter.OnCpuLoadUpdated(1, 1, 0.99f, 0.99f); + EXPECT_EQ(640, adapter.output_format().width); + EXPECT_EQ(400, adapter.output_format().height); + + // Encoder resolution request: downgrade with different size. Adapt nothing. + adapter.OnEncoderResolutionRequest(320, 200, + CoordinatedVideoAdapter::DOWNGRADE); + EXPECT_EQ(640, adapter.output_format().width); + EXPECT_EQ(400, adapter.output_format().height); + + // Encoder resolution request: downgrade. + adapter.OnEncoderResolutionRequest(640, 400, + CoordinatedVideoAdapter::DOWNGRADE); + EXPECT_EQ(480, adapter.output_format().width); + EXPECT_EQ(300, adapter.output_format().height); + + // Encoder resolution request: downgrade. But GD off. Adapt nothing. + adapter.set_gd_adaptation(false); + adapter.OnEncoderResolutionRequest(480, 300, + CoordinatedVideoAdapter::DOWNGRADE); + EXPECT_EQ(480, adapter.output_format().width); + EXPECT_EQ(300, adapter.output_format().height); + adapter.set_gd_adaptation(true); + + // Encoder resolution request: downgrade. + adapter.OnEncoderResolutionRequest(480, 300, + CoordinatedVideoAdapter::DOWNGRADE); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(200, adapter.output_format().height); + + // Encoder resolution request: keep. Adapt nothing. + adapter.OnEncoderResolutionRequest(320, 200, + CoordinatedVideoAdapter::KEEP); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(200, adapter.output_format().height); + + // Encoder resolution request: upgrade. + adapter.OnEncoderResolutionRequest(320, 200, + CoordinatedVideoAdapter::UPGRADE); + EXPECT_EQ(480, adapter.output_format().width); + EXPECT_EQ(300, adapter.output_format().height); + + // Server format request 0x0. + format.width = 0; + format.height = 0; + adapter.OnOutputFormatRequest(format); + EXPECT_TRUE(adapter.output_format().IsSize0x0()); + + // Server format request 320x200. + format.width = 320; + format.height = 200; + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(200, adapter.output_format().height); + + // Server format request 160x100. But view disabled. Adapt nothing. + adapter.set_view_adaptation(false); + format.width = 160; + format.height = 100; + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(200, adapter.output_format().height); + adapter.set_view_adaptation(true); + + // Enable View Switch. Expect adapt down. + adapter.set_view_switch(true); + format.width = 160; + format.height = 100; + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(160, adapter.output_format().width); + EXPECT_EQ(100, adapter.output_format().height); + + // Encoder resolution request: upgrade. Adapt nothing. + adapter.OnEncoderResolutionRequest(160, 100, + CoordinatedVideoAdapter::UPGRADE); + EXPECT_EQ(160, adapter.output_format().width); + EXPECT_EQ(100, adapter.output_format().height); + + // Request View of 2 / 3. Expect adapt down. + adapter.set_view_switch(true); + format.width = (640 * 2 + 1) / 3; + format.height = (400 * 2 + 1) / 3; + adapter.OnOutputFormatRequest(format); + EXPECT_EQ((640 * 2 + 1) / 3, adapter.output_format().width); + EXPECT_EQ((400 * 2 + 1) / 3, adapter.output_format().height); + + + // Request View of 3 / 8. Expect adapt down. + adapter.set_view_switch(true); + format.width = 640 * 3 / 8; + format.height = 400 * 3 / 8; + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(640 * 3 / 8, adapter.output_format().width); + EXPECT_EQ(400 * 3 / 8, adapter.output_format().height); + + // View Switch back up. Expect adapt. + format.width = 320; + format.height = 200; + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(200, adapter.output_format().height); + + adapter.set_view_switch(false); + + // Encoder resolution request: upgrade. Constrained by server request. + adapter.OnEncoderResolutionRequest(320, 200, + CoordinatedVideoAdapter::UPGRADE); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(200, adapter.output_format().height); + + // Server format request 480x300. + format.width = 480; + format.height = 300; + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(480, adapter.output_format().width); + EXPECT_EQ(300, adapter.output_format().height); +} + +TEST(CoordinatedVideoAdapterTest, TestCoordinatedWithCpuAdaptation) { + CoordinatedVideoAdapter adapter; + adapter.set_cpu_adaptation(true); + EXPECT_FALSE(adapter.cpu_smoothing()); + VideoFormat format(640, 400, VideoFormat::FpsToInterval(30), FOURCC_I420); + adapter.SetInputFormat(format); + + // Server format request 640x400. + format.height = 400; + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(640, adapter.output_format().width); + EXPECT_EQ(400, adapter.output_format().height); + + // Process load is medium, but system load is high. Downgrade. + UpdateCpuLoad(&adapter, 1, 1, 0.55f, 0.98f); + EXPECT_EQ(480, adapter.output_format().width); + EXPECT_EQ(300, adapter.output_format().height); + + // CPU high, but cpu adaptation disabled. Adapt nothing. + adapter.set_cpu_adaptation(false); + adapter.OnCpuLoadUpdated(1, 1, 0.55f, 0.98f); + EXPECT_EQ(480, adapter.output_format().width); + EXPECT_EQ(300, adapter.output_format().height); + adapter.set_cpu_adaptation(true); + + // System load is high, but time has not elaspsed. Adapt nothing. + adapter.set_cpu_load_min_samples(2); + adapter.OnCpuLoadUpdated(1, 1, 0.55f, 0.98f); + EXPECT_EQ(480, adapter.output_format().width); + EXPECT_EQ(300, adapter.output_format().height); + + // Process load is medium, but system load is high. Downgrade. + UpdateCpuLoad(&adapter, 1, 1, 0.55f, 0.98f); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(200, adapter.output_format().height); + + // Test reason for adapting is CPU. + EXPECT_EQ(CoordinatedVideoAdapter::ADAPTREASON_CPU, + adapter.adapt_reason()); + + // Server format request 320x200. Same as CPU. Do nothing. + format.width = 320; + format.height = 200; + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(200, adapter.output_format().height); + + // Test reason for adapting is CPU and VIEW. + EXPECT_EQ(CoordinatedVideoAdapter::ADAPTREASON_CPU + + CoordinatedVideoAdapter::ADAPTREASON_VIEW, + adapter.adapt_reason()); + + // Process load and system load are normal. Adapt nothing. + UpdateCpuLoad(&adapter, 1, 1, 0.5f, 0.8f); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(200, adapter.output_format().height); + + // Process load and system load are low, but view is still low. Adapt nothing. + UpdateCpuLoad(&adapter, 1, 1, 0.2f, 0.3f); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(200, adapter.output_format().height); + + // Test reason for adapting is VIEW. + EXPECT_EQ(CoordinatedVideoAdapter::ADAPTREASON_VIEW, + adapter.adapt_reason()); + + // Server format request 640x400. Cpu is still low. Upgrade. + format.width = 640; + format.height = 400; + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(480, adapter.output_format().width); + EXPECT_EQ(300, adapter.output_format().height); + + // Test reason for adapting is CPU. + EXPECT_EQ(CoordinatedVideoAdapter::ADAPTREASON_CPU, + adapter.adapt_reason()); + + // Encoder resolution request: downgrade. + adapter.OnEncoderResolutionRequest(480, 300, + CoordinatedVideoAdapter::DOWNGRADE); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(200, adapter.output_format().height); + + // Test reason for adapting is BANDWIDTH. + EXPECT_EQ(CoordinatedVideoAdapter::ADAPTREASON_BANDWIDTH, + adapter.adapt_reason()); + + // Process load and system load are low. Constrained by GD. Adapt nothing + adapter.OnCpuLoadUpdated(1, 1, 0.2f, 0.3f); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(200, adapter.output_format().height); + + // Encoder resolution request: upgrade. + adapter.OnEncoderResolutionRequest(320, 200, + CoordinatedVideoAdapter::UPGRADE); + EXPECT_EQ(480, adapter.output_format().width); + EXPECT_EQ(300, adapter.output_format().height); + + // Encoder resolution request: upgrade. Constrained by CPU. + adapter.OnEncoderResolutionRequest(480, 300, + CoordinatedVideoAdapter::UPGRADE); + EXPECT_EQ(480, adapter.output_format().width); + EXPECT_EQ(300, adapter.output_format().height); + + // Server format request 640x400. Constrained by CPU. + format.width = 640; + format.height = 400; + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(480, adapter.output_format().width); + EXPECT_EQ(300, adapter.output_format().height); +} + +TEST(CoordinatedVideoAdapterTest, TestCoordinatedWithCpuRequest) { + CoordinatedVideoAdapter adapter; + adapter.set_cpu_adaptation(true); + EXPECT_FALSE(adapter.cpu_smoothing()); + VideoFormat format(640, 400, VideoFormat::FpsToInterval(30), FOURCC_I420); + adapter.SetInputFormat(format); + + // Server format request 640x400. + format.height = 400; + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(640, adapter.output_format().width); + EXPECT_EQ(400, adapter.output_format().height); + + // CPU resolution request: downgrade. Adapt down. + adapter.OnCpuResolutionRequest(CoordinatedVideoAdapter::DOWNGRADE); + EXPECT_EQ(480, adapter.output_format().width); + EXPECT_EQ(300, adapter.output_format().height); + + // CPU resolution request: keep. Do nothing. + adapter.OnCpuResolutionRequest(CoordinatedVideoAdapter::KEEP); + EXPECT_EQ(480, adapter.output_format().width); + EXPECT_EQ(300, adapter.output_format().height); + + // CPU resolution request: downgrade, but cpu adaptation disabled. + // Adapt nothing. + adapter.set_cpu_adaptation(false); + adapter.OnCpuResolutionRequest(CoordinatedVideoAdapter::DOWNGRADE); + EXPECT_EQ(480, adapter.output_format().width); + EXPECT_EQ(300, adapter.output_format().height); + + // CPU resolution request: downgrade. Adapt down. + adapter.set_cpu_adaptation(true); + adapter.OnCpuResolutionRequest(CoordinatedVideoAdapter::DOWNGRADE); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(200, adapter.output_format().height); + + // Test reason for adapting is CPU. + EXPECT_EQ(CoordinatedVideoAdapter::ADAPTREASON_CPU, + adapter.adapt_reason()); + + // CPU resolution request: downgrade, but already at minimum. Do nothing. + adapter.OnCpuResolutionRequest(CoordinatedVideoAdapter::DOWNGRADE); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(200, adapter.output_format().height); + + // Server format request 320x200. Same as CPU. Do nothing. + format.width = 320; + format.height = 200; + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(200, adapter.output_format().height); + + // Test reason for adapting is CPU and VIEW. + EXPECT_EQ(CoordinatedVideoAdapter::ADAPTREASON_CPU + + CoordinatedVideoAdapter::ADAPTREASON_VIEW, + adapter.adapt_reason()); + + // CPU resolution request: upgrade, but view request still low. Do nothing. + adapter.OnCpuResolutionRequest(CoordinatedVideoAdapter::UPGRADE); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(200, adapter.output_format().height); + + // Test reason for adapting is VIEW. + EXPECT_EQ(CoordinatedVideoAdapter::ADAPTREASON_VIEW, + adapter.adapt_reason()); + + // Server format request 640x400. Cpu is still low. Upgrade. + format.width = 640; + format.height = 400; + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(480, adapter.output_format().width); + EXPECT_EQ(300, adapter.output_format().height); + + // Test reason for adapting is CPU. + EXPECT_EQ(CoordinatedVideoAdapter::ADAPTREASON_CPU, + adapter.adapt_reason()); + + // Encoder resolution request: downgrade. + adapter.OnEncoderResolutionRequest(480, 300, + CoordinatedVideoAdapter::DOWNGRADE); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(200, adapter.output_format().height); + + // Test reason for adapting is BANDWIDTH. + EXPECT_EQ(CoordinatedVideoAdapter::ADAPTREASON_BANDWIDTH, + adapter.adapt_reason()); + + // Process load and system load are low. Constrained by GD. Adapt nothing + adapter.OnCpuLoadUpdated(1, 1, 0.2f, 0.3f); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(200, adapter.output_format().height); + + // Encoder resolution request: upgrade. + adapter.OnEncoderResolutionRequest(320, 200, + CoordinatedVideoAdapter::UPGRADE); + EXPECT_EQ(480, adapter.output_format().width); + EXPECT_EQ(300, adapter.output_format().height); + + // Encoder resolution request: upgrade. Constrained by CPU. + adapter.OnEncoderResolutionRequest(480, 300, + CoordinatedVideoAdapter::UPGRADE); + EXPECT_EQ(480, adapter.output_format().width); + EXPECT_EQ(300, adapter.output_format().height); + + // Server format request 640x400. Constrained by CPU. + format.width = 640; + format.height = 400; + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(480, adapter.output_format().width); + EXPECT_EQ(300, adapter.output_format().height); +} + +TEST(CoordinatedVideoAdapterTest, TestViewRequestPlusCameraSwitch) { + CoordinatedVideoAdapter adapter; + adapter.set_view_switch(true); + + // Start at HD. + VideoFormat format(1280, 720, VideoFormat::FpsToInterval(30), FOURCC_I420); + adapter.SetInputFormat(format); + EXPECT_EQ(format, adapter.input_format()); + EXPECT_TRUE(adapter.output_format().IsSize0x0()); + + // View request for VGA. + format.width = 640; + format.height = 360; + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(640, adapter.output_format().width); + EXPECT_EQ(360, adapter.output_format().height); + EXPECT_EQ(CoordinatedVideoAdapter::ADAPTREASON_VIEW, adapter.adapt_reason()); + + // Now, the camera reopens at VGA. + // Both the frame and the output format should be 640x360. + WebRtcVideoFrame in_frame; + in_frame.InitToBlack(640, 360, 1, 1, 33, 33); + VideoFrame* out_frame; + adapter.AdaptFrame(&in_frame, &out_frame); + EXPECT_EQ(640u, out_frame->GetWidth()); + EXPECT_EQ(360u, out_frame->GetHeight()); + // At this point, the view is no longer adapted, since the input has resized + // small enough to fit the last view request. + EXPECT_EQ(0, adapter.adapt_reason()); + + // And another view request comes in for 640x360, which should have no + // real impact. + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(640, adapter.output_format().width); + EXPECT_EQ(360, adapter.output_format().height); + EXPECT_EQ(0, adapter.adapt_reason()); +} + +TEST(CoordinatedVideoAdapterTest, TestVGAWidth) { + CoordinatedVideoAdapter adapter; + adapter.set_view_switch(true); + + // Start at 640x480, for cameras that don't support 640x360. + VideoFormat format(640, 480, VideoFormat::FpsToInterval(30), FOURCC_I420); + adapter.SetInputFormat(format); + EXPECT_EQ(format, adapter.input_format()); + EXPECT_TRUE(adapter.output_format().IsSize0x0()); + + // Output format is 640x360, though. + format.width = 640; + format.height = 360; + adapter.SetOutputFormat(format); + + // And also a view request comes for 640x360. + adapter.OnOutputFormatRequest(format); + // At this point, we have to adapt down to something lower. + EXPECT_EQ(480, adapter.output_format().width); + EXPECT_EQ(360, adapter.output_format().height); + + // But if frames come in at 640x360, we shouldn't adapt them down. + // Fake a 640x360 frame. + WebRtcVideoFrame in_frame; + in_frame.InitToBlack(640, 360, 1, 1, 33, 33); + VideoFrame* out_frame; + adapter.AdaptFrame(&in_frame, &out_frame); + + EXPECT_EQ(640u, out_frame->GetWidth()); + EXPECT_EQ(360u, out_frame->GetHeight()); + + // Similarly, no-op adapt requests for other reasons shouldn't change + // adaptation state (before a previous bug, the previous EXPECTs would + // fail and the following would succeed, as the no-op CPU request would + // fix the adaptation state). + adapter.set_cpu_adaptation(true); + UpdateCpuLoad(&adapter, 1, 1, 0.7f, 0.7f); + adapter.AdaptFrame(&in_frame, &out_frame); + + EXPECT_EQ(640u, out_frame->GetWidth()); + EXPECT_EQ(360u, out_frame->GetHeight()); +} + +// When adapting resolution for CPU or GD, the quantity of pixels that the +// request is based on is reduced to half or double, and then an actual +// resolution is snapped to, rounding to the closest actual resolution. +// This works well for some tolerance to 3/4, odd widths and aspect ratios +// that dont exactly match, but is not best behavior for ViewRequests which +// need to be be strictly respected to avoid going over the resolution budget +// given to the codec - 854x480 total pixels. +// ViewRequest must find a lower resolution. +TEST(CoordinatedVideoAdapterTest, TestCoordinatedViewRequestDown) { + CoordinatedVideoAdapter adapter; + adapter.set_cpu_adaptation(false); + + VideoFormat format(960, 540, VideoFormat::FpsToInterval(30), FOURCC_I420); + adapter.SetInputFormat(format); + adapter.set_scale_third(true); + EXPECT_EQ(format, adapter.input_format()); + EXPECT_TRUE(adapter.output_format().IsSize0x0()); + + // Server format request 640x400. Expect HVGA. + format.width = 640; + format.height = 400; + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(640, adapter.output_format().width); + EXPECT_EQ(360, adapter.output_format().height); + + // Test reason for adapting is VIEW. + EXPECT_EQ(CoordinatedVideoAdapter::ADAPTREASON_VIEW, adapter.adapt_reason()); +} + +// Test that we downgrade video for cpu up to two times. +TEST(CoordinatedVideoAdapterTest, TestCpuDowngradeTimes) { + CoordinatedVideoAdapter adapter; + adapter.set_cpu_adaptation(true); + EXPECT_FALSE(adapter.cpu_smoothing()); + VideoFormat format(640, 400, VideoFormat::FpsToInterval(30), FOURCC_I420); + adapter.SetInputFormat(format); + + // Server format request 640x400. + format.height = 400; + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(640, adapter.output_format().width); + EXPECT_EQ(400, adapter.output_format().height); + + // Process load and system load are low. Do not change the cpu desired format + // and do not adapt. + adapter.OnCpuLoadUpdated(1, 1, 0.2f, 0.3f); + EXPECT_EQ(640, adapter.output_format().width); + EXPECT_EQ(400, adapter.output_format().height); + + // System load is high. Downgrade. + UpdateCpuLoad(&adapter, 1, 1, 0.95f, 0.95f); + EXPECT_EQ(480, adapter.output_format().width); + EXPECT_EQ(300, adapter.output_format().height); + + // System load is high. Downgrade again. + UpdateCpuLoad(&adapter, 1, 1, 0.95f, 0.95f); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(200, adapter.output_format().height); + + // System load is still high. Do not downgrade any more. + UpdateCpuLoad(&adapter, 1, 1, 0.95f, 0.95f); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(200, adapter.output_format().height); + + // Process load and system load are low. Upgrade. + UpdateCpuLoad(&adapter, 1, 1, 0.2f, 0.3f); + EXPECT_EQ(480, adapter.output_format().width); + EXPECT_EQ(300, adapter.output_format().height); + + // System load is high. Downgrade. + UpdateCpuLoad(&adapter, 1, 1, 0.95f, 0.95f); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(200, adapter.output_format().height); + + // System load is still high. Do not downgrade any more. + UpdateCpuLoad(&adapter, 1, 1, 0.95f, 0.95f); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(200, adapter.output_format().height); +} + +// Test that we respect CPU adapter threshold values. +TEST(CoordinatedVideoAdapterTest, TestAdapterCpuThreshold) { + CoordinatedVideoAdapter adapter; + adapter.set_cpu_adaptation(true); + EXPECT_FALSE(adapter.cpu_smoothing()); + VideoFormat format(640, 400, VideoFormat::FpsToInterval(30), FOURCC_I420); + adapter.SetInputFormat(format); + + // Server format request 640x400. + format.height = 400; + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(640, adapter.output_format().width); + EXPECT_EQ(400, adapter.output_format().height); + + // Process load and system load are low. Do not change the cpu desired format + // and do not adapt. + adapter.OnCpuLoadUpdated(1, 1, 0.2f, 0.3f); + EXPECT_EQ(640, adapter.output_format().width); + EXPECT_EQ(400, adapter.output_format().height); + + // System load is high. Downgrade. + UpdateCpuLoad(&adapter, 1, 1, 0.95f, 0.95f); + EXPECT_EQ(480, adapter.output_format().width); + EXPECT_EQ(300, adapter.output_format().height); + + // Test reason for adapting is CPU. + EXPECT_EQ(CoordinatedVideoAdapter::ADAPTREASON_CPU, adapter.adapt_reason()); + + // System load is high. Normally downgrade but threshold is high. Do nothing. + adapter.set_high_system_threshold(0.98f); // Set threshold high. + UpdateCpuLoad(&adapter, 1, 1, 0.95f, 0.95f); + EXPECT_EQ(480, adapter.output_format().width); + EXPECT_EQ(300, adapter.output_format().height); + + // System load is medium. Normally do nothing, threshold is low. Adapt down. + adapter.set_high_system_threshold(0.75f); // Set threshold low. + UpdateCpuLoad(&adapter, 1, 1, 0.8f, 0.8f); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(200, adapter.output_format().height); +} + + +// Test that for an upgrade cpu request, we actually upgrade the desired format; +// for a downgrade request, we downgrade from the output format. +TEST(CoordinatedVideoAdapterTest, TestRealCpuUpgrade) { + CoordinatedVideoAdapter adapter; + adapter.set_cpu_adaptation(true); + adapter.set_cpu_smoothing(true); + VideoFormat format(640, 400, VideoFormat::FpsToInterval(30), FOURCC_I420); + adapter.SetInputFormat(format); + + // Server format request 640x400. + format.width = 640; + format.height = 400; + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(640, adapter.output_format().width); + EXPECT_EQ(400, adapter.output_format().height); + + // Process load and system load are low. Do not change the cpu desired format + // and do not adapt. + UpdateCpuLoad(&adapter, 1, 1, 0.2f, 0.3f); + EXPECT_EQ(640, adapter.output_format().width); + EXPECT_EQ(400, adapter.output_format().height); + + // Server format request 320x200. + format.width = 320; + format.height = 200; + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(200, adapter.output_format().height); + + // Process load and system load are low. Do not change the cpu desired format + // and do not adapt. + UpdateCpuLoad(&adapter, 1, 1, 0.2f, 0.3f); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(200, adapter.output_format().height); + + // Server format request 640x400. Set to 640x400 immediately. + format.width = 640; + format.height = 400; + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(640, adapter.output_format().width); + EXPECT_EQ(400, adapter.output_format().height); + + // Server format request 320x200. + format.width = 320; + format.height = 200; + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(200, adapter.output_format().height); + + // Process load is high, but system is not. Do not change the cpu desired + // format and do not adapt. + for (size_t i = 0; i < 10; ++i) { + UpdateCpuLoad(&adapter, 1, 1, 0.75f, 0.8f); + } + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(200, adapter.output_format().height); +} + +// Test that for an upgrade encoder request, we actually upgrade the desired +// format; for a downgrade request, we downgrade from the output format. +TEST(CoordinatedVideoAdapterTest, TestRealEncoderUpgrade) { + CoordinatedVideoAdapter adapter; + adapter.set_cpu_adaptation(true); + adapter.set_cpu_smoothing(true); + VideoFormat format(640, 400, VideoFormat::FpsToInterval(30), FOURCC_I420); + adapter.SetInputFormat(format); + + // Server format request 640x400. + format.width = 640; + format.height = 400; + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(640, adapter.output_format().width); + EXPECT_EQ(400, adapter.output_format().height); + + // Encoder resolution request. Do not change the encoder desired format and + // do not adapt. + adapter.OnEncoderResolutionRequest(640, 400, + CoordinatedVideoAdapter::UPGRADE); + EXPECT_EQ(640, adapter.output_format().width); + EXPECT_EQ(400, adapter.output_format().height); + + // Server format request 320x200. + format.width = 320; + format.height = 200; + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(200, adapter.output_format().height); + + // Encoder resolution request. Do not change the encoder desired format and + // do not adapt. + adapter.OnEncoderResolutionRequest(320, 200, + CoordinatedVideoAdapter::UPGRADE); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(200, adapter.output_format().height); + + // Server format request 640x400. Set to 640x400 immediately. + format.width = 640; + format.height = 400; + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(480, adapter.output_format().width); + EXPECT_EQ(300, adapter.output_format().height); + + // Test reason for adapting is BANDWIDTH. + EXPECT_EQ(CoordinatedVideoAdapter::ADAPTREASON_BANDWIDTH, + adapter.adapt_reason()); + + // Server format request 320x200. + format.width = 320; + format.height = 200; + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(200, adapter.output_format().height); + + // Encoder resolution request. Downgrade from 320x200. + adapter.OnEncoderResolutionRequest(320, 200, + CoordinatedVideoAdapter::DOWNGRADE); + EXPECT_EQ(240, adapter.output_format().width); + EXPECT_EQ(150, adapter.output_format().height); +} + +TEST(CoordinatedVideoAdapterTest, TestNormalizeOutputFormat) { + CoordinatedVideoAdapter adapter; + // The input format is 640x360 and the output is limited to 16:9. + VideoFormat format(640, 360, VideoFormat::FpsToInterval(30), FOURCC_I420); + adapter.SetInputFormat(format); + + format.width = 320; + format.height = 180; + format.interval = VideoFormat::FpsToInterval(15); + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(180, adapter.output_format().height); + EXPECT_EQ(VideoFormat::FpsToInterval(15), adapter.output_format().interval); + + format.width = 320; + format.height = 200; + format.interval = VideoFormat::FpsToInterval(40); + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(180, adapter.output_format().height); + EXPECT_EQ(VideoFormat::FpsToInterval(30), adapter.output_format().interval); + + // Test reason for adapting is VIEW. Should work even with normalization. + EXPECT_EQ(CoordinatedVideoAdapter::ADAPTREASON_VIEW, + adapter.adapt_reason()); + + format.width = 320; + format.height = 240; + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(180, adapter.output_format().height); + + // The input format is 640x480 and the output will be 4:3. + format.width = 640; + format.height = 480; + adapter.SetInputFormat(format); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(240, adapter.output_format().height); + + format.width = 320; + format.height = 240; + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(240, adapter.output_format().height); + + // The input format is initialized after the output. At that time, the output + // height is adjusted. + format.width = 0; + format.height = 0; + adapter.SetInputFormat(format); + + format.width = 320; + format.height = 240; + format.interval = VideoFormat::FpsToInterval(30); + adapter.OnOutputFormatRequest(format); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(240, adapter.output_format().height); + EXPECT_EQ(VideoFormat::FpsToInterval(30), adapter.output_format().interval); + + format.width = 640; + format.height = 480; + format.interval = VideoFormat::FpsToInterval(15); + adapter.SetInputFormat(format); + EXPECT_EQ(320, adapter.output_format().width); + EXPECT_EQ(240, adapter.output_format().height); + EXPECT_EQ(VideoFormat::FpsToInterval(15), adapter.output_format().interval); +} + +// Test that we downgrade video for cpu up to two times. +TEST_F(VideoAdapterTest, CpuDowngradeAndSignal) { + CoordinatedVideoAdapter adapter; + CpuAdapterListener cpu_listener; + adapter.SignalCpuAdaptationUnable.connect( + &cpu_listener, &CpuAdapterListener::OnCpuAdaptationSignalled); + + adapter.set_cpu_adaptation(true); + EXPECT_FALSE(adapter.cpu_smoothing()); + VideoFormat format(640, 360, VideoFormat::FpsToInterval(30), FOURCC_I420); + adapter.SetInputFormat(format); + adapter.OnOutputFormatRequest(format); + + // System load is high. Downgrade. + UpdateCpuLoad(&adapter, 1, 1, 0.95f, 0.95f); + + // System load is high. Downgrade again. + UpdateCpuLoad(&adapter, 1, 1, 0.95f, 0.95f); + + // System load is still high. Do not downgrade any more. Ensure we have not + // signalled until after the cpu warning though. + EXPECT_TRUE(!cpu_listener.received_cpu_signal()); + UpdateCpuLoad(&adapter, 1, 1, 0.95f, 0.95f); + EXPECT_TRUE_WAIT(cpu_listener.received_cpu_signal(), kWaitTimeout); +} + +// Test that we downgrade video for cpu up to two times. +TEST_F(VideoAdapterTest, CpuDowngradeAndDontSignal) { + CoordinatedVideoAdapter adapter; + CpuAdapterListener cpu_listener; + adapter.SignalCpuAdaptationUnable.connect( + &cpu_listener, &CpuAdapterListener::OnCpuAdaptationSignalled); + + adapter.set_cpu_adaptation(true); + adapter.set_cpu_smoothing(true); + VideoFormat format(640, 360, VideoFormat::FpsToInterval(30), FOURCC_I420); + adapter.SetInputFormat(format); + adapter.OnOutputFormatRequest(format); + + // System load is high. Downgrade. + UpdateCpuLoad(&adapter, 1, 1, 0.95f, 0.95f); + + // System load is high, process is not, Do not downgrade again. + UpdateCpuLoad(&adapter, 1, 1, 0.25f, 0.95f); + + // System load is high, process is not, Do not downgrade again and do not + // signal. + adapter.set_cpu_adaptation(false); + UpdateCpuLoad(&adapter, 1, 1, 0.95f, 0.95f); + rtc::Thread::Current()->ProcessMessages(kShortWaitTimeout); + EXPECT_TRUE(!cpu_listener.received_cpu_signal()); + adapter.set_cpu_adaptation(true); +} + +// Test that we require enough time before we downgrade. +TEST_F(VideoAdapterTest, CpuMinTimeRequirement) { + CoordinatedVideoAdapter adapter; + CpuAdapterListener cpu_listener; + adapter.SignalCpuAdaptationUnable.connect( + &cpu_listener, &CpuAdapterListener::OnCpuAdaptationSignalled); + + adapter.set_cpu_adaptation(true); + adapter.set_cpu_smoothing(true); + VideoFormat format(640, 360, VideoFormat::FpsToInterval(30), FOURCC_I420); + adapter.SetInputFormat(format); + adapter.OnOutputFormatRequest(format); + + EXPECT_EQ(3, adapter.cpu_load_min_samples()); + adapter.set_cpu_load_min_samples(5); + + for (size_t i = 0; i < 4; ++i) { + adapter.OnCpuLoadUpdated(1, 1, 1.0f, 1.0f); + EXPECT_EQ(640, adapter.output_format().width); + EXPECT_EQ(360, adapter.output_format().height); + } + // The computed cpu load should now be around 93.5%, with the coefficient of + // 0.4 and a seed value of 0.5. That should be high enough to adapt, but it + // isn't enough samples, so we shouldn't have adapted on any of the previous + // samples. + + // One more sample is enough, though, once enough time has passed. + adapter.OnCpuLoadUpdated(1, 1, 1.0f, 1.0f); + EXPECT_EQ(480, adapter.output_format().width); + EXPECT_EQ(270, adapter.output_format().height); + + // Now the cpu is lower, but we still need enough samples to upgrade. + for (size_t i = 0; i < 4; ++i) { + adapter.OnCpuLoadUpdated(1, 1, 0.1f, 0.1f); + EXPECT_EQ(480, adapter.output_format().width); + EXPECT_EQ(270, adapter.output_format().height); + } + + // One more sample is enough, once time has elapsed. + adapter.OnCpuLoadUpdated(1, 1, 1.0f, 1.0f); + EXPECT_EQ(640, adapter.output_format().width); + EXPECT_EQ(360, adapter.output_format().height); +} + +TEST_F(VideoAdapterTest, CpuIgnoresSpikes) { + CoordinatedVideoAdapter adapter; + CpuAdapterListener cpu_listener; + adapter.SignalCpuAdaptationUnable.connect( + &cpu_listener, &CpuAdapterListener::OnCpuAdaptationSignalled); + + adapter.set_cpu_adaptation(true); + adapter.set_cpu_smoothing(true); + VideoFormat format(640, 360, VideoFormat::FpsToInterval(30), FOURCC_I420); + adapter.SetInputFormat(format); + adapter.OnOutputFormatRequest(format); + + // System load is high. Downgrade. + for (size_t i = 0; i < 5; ++i) { + UpdateCpuLoad(&adapter, 1, 1, 0.95f, 0.95f); + } + EXPECT_EQ(480, adapter.output_format().width); + EXPECT_EQ(270, adapter.output_format().height); + + // Now we're in a state where we could upgrade or downgrade, so get to a + // steady state of about 75% cpu usage. + for (size_t i = 0; i < 5; ++i) { + UpdateCpuLoad(&adapter, 1, 1, 0.75f, 0.75f); + EXPECT_EQ(480, adapter.output_format().width); + EXPECT_EQ(270, adapter.output_format().height); + } + + // Now, the cpu spikes for two samples, but then goes back to + // normal. This shouldn't cause adaptation. + UpdateCpuLoad(&adapter, 1, 1, 0.90f, 0.90f); + UpdateCpuLoad(&adapter, 1, 1, 0.90f, 0.90f); + EXPECT_EQ(480, adapter.output_format().width); + EXPECT_EQ(270, adapter.output_format().height); + // Back to the steady state for awhile. + for (size_t i = 0; i < 5; ++i) { + UpdateCpuLoad(&adapter, 1, 1, 0.75, 0.75); + EXPECT_EQ(480, adapter.output_format().width); + EXPECT_EQ(270, adapter.output_format().height); + } + + // Now, system cpu usage is starting to drop down. But it takes a bit before + // it gets all the way there. + for (size_t i = 0; i < 10; ++i) { + UpdateCpuLoad(&adapter, 1, 1, 0.5f, 0.5f); + } + EXPECT_EQ(640, adapter.output_format().width); + EXPECT_EQ(360, adapter.output_format().height); +} + +} // namespace cricket +#endif // HAVE_WEBRTC_VIDEO diff --git a/talk/media/base/videocapturer.cc b/talk/media/base/videocapturer.cc index 139fd09a5..f20e6fb76 100644 --- a/talk/media/base/videocapturer.cc +++ b/talk/media/base/videocapturer.cc @@ -35,13 +35,14 @@ #include "webrtc/base/common.h" #include "webrtc/base/logging.h" #include "webrtc/base/systeminfo.h" +#include "talk/media/base/videoframefactory.h" #include "talk/media/base/videoprocessor.h" #if defined(HAVE_WEBRTC_VIDEO) #include "talk/media/webrtc/webrtcvideoframe.h" +#include "talk/media/webrtc/webrtcvideoframefactory.h" #endif // HAVE_WEBRTC_VIDEO - namespace cricket { namespace { @@ -352,10 +353,6 @@ void VideoCapturer::OnFrameCaptured(VideoCapturer*, if (SignalVideoFrame.is_empty()) { return; } -#if defined(HAVE_WEBRTC_VIDEO) -#define VIDEO_FRAME_NAME WebRtcVideoFrame -#endif -#if defined(VIDEO_FRAME_NAME) #if !defined(DISABLE_YUV) if (IsScreencast()) { int scaled_width, scaled_height; @@ -501,8 +498,15 @@ void VideoCapturer::OnFrameCaptured(VideoCapturer*, &desired_width, &desired_height); } - VIDEO_FRAME_NAME i420_frame; - if (!i420_frame.Alias(captured_frame, desired_width, desired_height)) { + if (!frame_factory_) { + LOG(LS_ERROR) << "No video frame factory."; + return; + } + + rtc::scoped_ptr i420_frame( + frame_factory_->CreateAliasedFrame( + captured_frame, desired_width, desired_height)); + if (!i420_frame) { // TODO(fbarchard): LOG more information about captured frame attributes. LOG(LS_ERROR) << "Couldn't convert to I420! " << "From " << ToString(captured_frame) << " To " @@ -510,7 +514,7 @@ void VideoCapturer::OnFrameCaptured(VideoCapturer*, return; } - VideoFrame* adapted_frame = &i420_frame; + VideoFrame* adapted_frame = i420_frame.get(); if (enable_video_adapter_ && !IsScreencast()) { VideoFrame* out_frame = NULL; video_adapter_.AdaptFrame(adapted_frame, &out_frame); @@ -528,13 +532,12 @@ void VideoCapturer::OnFrameCaptured(VideoCapturer*, return; } if (muted_) { + // TODO(pthatcher): Use frame_factory_->CreateBlackFrame() instead. adapted_frame->SetToBlack(); } SignalVideoFrame(this, adapted_frame); UpdateStats(captured_frame); - -#endif // VIDEO_FRAME_NAME } void VideoCapturer::SetCaptureState(CaptureState state) { diff --git a/talk/media/base/videocapturer.h b/talk/media/base/videocapturer.h index d4192be58..c60221a55 100644 --- a/talk/media/base/videocapturer.h +++ b/talk/media/base/videocapturer.h @@ -42,6 +42,7 @@ #include "talk/media/base/mediachannel.h" #include "talk/media/base/videoadapter.h" #include "talk/media/base/videocommon.h" +#include "talk/media/base/videoframefactory.h" #include "talk/media/devices/devicemanager.h" @@ -289,6 +290,11 @@ class VideoCapturer return &video_adapter_; } + // Takes ownership. + void set_frame_factory(VideoFrameFactory* frame_factory) { + frame_factory_.reset(frame_factory); + } + // Gets statistics for tracked variables recorded since the last call to // GetStats. Note that calling GetStats resets any gathered data so it // should be called only periodically to log statistics. @@ -326,6 +332,7 @@ class VideoCapturer } void SetSupportedFormats(const std::vector& formats); + VideoFrameFactory* frame_factory() { return frame_factory_.get(); } private: void Construct(); @@ -361,6 +368,7 @@ class VideoCapturer rtc::Thread* thread_; std::string id_; CaptureState capture_state_; + rtc::scoped_ptr frame_factory_; rtc::scoped_ptr capture_format_; std::vector supported_formats_; rtc::scoped_ptr max_format_; diff --git a/talk/media/base/videocapturer_unittest.cc b/talk/media/base/videocapturer_unittest.cc index b70280f1c..37f6bbde4 100644 --- a/talk/media/base/videocapturer_unittest.cc +++ b/talk/media/base/videocapturer_unittest.cc @@ -1,4 +1,29 @@ -// Copyright 2008 Google Inc. +/* + * libjingle + * Copyright 2008 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 #include @@ -13,15 +38,6 @@ #include "talk/media/base/videocapturer.h" #include "talk/media/base/videoprocessor.h" -// If HAS_I420_FRAME is not defined the video capturer will not be able to -// provide OnVideoFrame-callbacks since they require cricket::CapturedFrame to -// be decoded as a cricket::VideoFrame (i.e. an I420 frame). This functionality -// only exist if HAS_I420_FRAME is defined below. I420 frames are also a -// requirement for the VideoProcessors so they will not be called either. -#if defined(HAVE_WEBRTC_VIDEO) -#define HAS_I420_FRAME -#endif - using cricket::FakeVideoCapturer; namespace { @@ -682,7 +698,12 @@ TEST_F(VideoCapturerTest, TestRequest16x10_9) { EXPECT_EQ(360, best.height); } -#if defined(HAS_I420_FRAME) +// If HAVE_WEBRTC_VIDEO is not defined the video capturer will not be able to +// provide OnVideoFrame-callbacks since they require cricket::CapturedFrame to +// be decoded as a cricket::VideoFrame (i.e. an I420 frame). This functionality +// only exist if HAVE_WEBRTC_VIDEO is defined below. I420 frames are also a +// requirement for the VideoProcessors so they will not be called either. +#if defined(HAVE_WEBRTC_VIDEO) TEST_F(VideoCapturerTest, VideoFrame) { EXPECT_EQ(cricket::CS_RUNNING, capturer_.Start(cricket::VideoFormat( 640, @@ -735,7 +756,7 @@ TEST_F(VideoCapturerTest, ProcessorDropFrame) { EXPECT_TRUE(capturer_.CaptureFrame()); EXPECT_EQ(0, video_frames_received()); } -#endif // HAS_I420_FRAME +#endif // HAVE_WEBRTC_VIDEO bool HdFormatInList(const std::vector& formats) { for (std::vector::const_iterator found = diff --git a/talk/media/base/videoengine_unittest.h b/talk/media/base/videoengine_unittest.h index 25811ba07..502cf47ce 100644 --- a/talk/media/base/videoengine_unittest.h +++ b/talk/media/base/videoengine_unittest.h @@ -503,14 +503,18 @@ class VideoMediaChannelTest : public testing::Test, media_error_ = cricket::VideoMediaChannel::ERROR_NONE; channel_->SetRecvCodecs(engine_.codecs()); EXPECT_TRUE(channel_->AddSendStream(DefaultSendStreamParams())); - - video_capturer_.reset(new cricket::FakeVideoCapturer); + video_capturer_.reset(CreateFakeVideoCapturer()); cricket::VideoFormat format(640, 480, cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420); EXPECT_EQ(cricket::CS_RUNNING, video_capturer_->Start(format)); EXPECT_TRUE(channel_->SetCapturer(kSsrc, video_capturer_.get())); } + + virtual cricket::FakeVideoCapturer* CreateFakeVideoCapturer() { + return new cricket::FakeVideoCapturer(); + } + // Utility method to setup an additional stream to send and receive video. // Used to test send and recv between two streams. void SetUpSecondStream() { @@ -535,7 +539,7 @@ class VideoMediaChannelTest : public testing::Test, // We dont add recv for the second stream. // Setup the receive and renderer for second stream after send. - video_capturer_2_.reset(new cricket::FakeVideoCapturer()); + video_capturer_2_.reset(CreateFakeVideoCapturer()); cricket::VideoFormat format(640, 480, cricket::VideoFormat::FpsToInterval(30), cricket::FOURCC_I420); @@ -967,7 +971,7 @@ class VideoMediaChannelTest : public testing::Test, // Add an additional capturer, and hook up a renderer to receive it. cricket::FakeVideoRenderer renderer1; rtc::scoped_ptr capturer( - new cricket::FakeVideoCapturer); + CreateFakeVideoCapturer()); capturer->SetScreencast(true); const int kTestWidth = 160; const int kTestHeight = 120; @@ -1317,7 +1321,7 @@ class VideoMediaChannelTest : public testing::Test, EXPECT_TRUE(SendFrame()); EXPECT_FRAME_WAIT(1, codec.width, codec.height, kTimeout); rtc::scoped_ptr capturer( - new cricket::FakeVideoCapturer); + CreateFakeVideoCapturer()); capturer->SetScreencast(true); cricket::VideoFormat format(480, 360, cricket::VideoFormat::FpsToInterval(30), @@ -1411,7 +1415,7 @@ class VideoMediaChannelTest : public testing::Test, EXPECT_TRUE(channel_->AddSendStream( cricket::StreamParams::CreateLegacy(1))); rtc::scoped_ptr capturer1( - new cricket::FakeVideoCapturer); + CreateFakeVideoCapturer()); capturer1->SetScreencast(true); EXPECT_EQ(cricket::CS_RUNNING, capturer1->Start(capture_format)); // Set up additional stream 2. @@ -1423,7 +1427,7 @@ class VideoMediaChannelTest : public testing::Test, EXPECT_TRUE(channel_->AddSendStream( cricket::StreamParams::CreateLegacy(2))); rtc::scoped_ptr capturer2( - new cricket::FakeVideoCapturer); + CreateFakeVideoCapturer()); capturer2->SetScreencast(true); EXPECT_EQ(cricket::CS_RUNNING, capturer2->Start(capture_format)); // State for all the streams. @@ -1481,7 +1485,7 @@ class VideoMediaChannelTest : public testing::Test, // Registering an external capturer is currently the same as screen casting // (update the test when this changes). rtc::scoped_ptr capturer( - new cricket::FakeVideoCapturer); + CreateFakeVideoCapturer()); capturer->SetScreencast(true); const std::vector* formats = capturer->GetSupportedFormats(); diff --git a/talk/media/base/videoframefactory.h b/talk/media/base/videoframefactory.h new file mode 100755 index 000000000..301ec1c43 --- /dev/null +++ b/talk/media/base/videoframefactory.h @@ -0,0 +1,52 @@ +// 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_VIDEOFRAMEFACTORY_H_ +#define TALK_MEDIA_BASE_VIDEOFRAMEFACTORY_H_ + +namespace cricket { + +struct CapturedFrame; +class VideoFrame; + +// Creates cricket::VideoFrames, or a subclass of cricket::VideoFrame +// depending on the subclass of VideoFrameFactory. +class VideoFrameFactory { + public: + VideoFrameFactory() {} + virtual ~VideoFrameFactory() {} + + // The returned frame aliases the aliased_frame if the input color + // space allows for aliasing, otherwise a color conversion will + // occur. For safety, |input_frame| must outlive the returned + // frame. Returns NULL if conversion fails. + virtual VideoFrame* CreateAliasedFrame( + const CapturedFrame* input_frame, int width, int height) const = 0; +}; + +} // namespace cricket + +#endif // TALK_MEDIA_BASE_VIDEOFRAMEFACTORY_H_ diff --git a/talk/media/webrtc/webrtcvideocapturer.cc b/talk/media/webrtc/webrtcvideocapturer.cc index d341d1205..38df9cebc 100644 --- a/talk/media/webrtc/webrtcvideocapturer.cc +++ b/talk/media/webrtc/webrtcvideocapturer.cc @@ -37,6 +37,7 @@ #include "webrtc/base/thread.h" #include "webrtc/base/timeutils.h" #include "talk/media/webrtc/webrtcvideoframe.h" +#include "talk/media/webrtc/webrtcvideoframefactory.h" #include "webrtc/base/win32.h" // Need this to #include the impl files. #include "webrtc/modules/video_capture/include/video_capture_factory.h" @@ -126,12 +127,14 @@ WebRtcVideoCapturer::WebRtcVideoCapturer() : factory_(new WebRtcVcmFactory), module_(NULL), captured_frames_(0) { + set_frame_factory(new WebRtcVideoFrameFactory()); } WebRtcVideoCapturer::WebRtcVideoCapturer(WebRtcVcmFactoryInterface* factory) : factory_(factory), module_(NULL), captured_frames_(0) { + set_frame_factory(new WebRtcVideoFrameFactory()); } WebRtcVideoCapturer::~WebRtcVideoCapturer() { diff --git a/talk/media/base/mutedvideocapturer.h b/talk/media/webrtc/webrtcvideoframefactory.cc similarity index 61% rename from talk/media/base/mutedvideocapturer.h rename to talk/media/webrtc/webrtcvideoframefactory.cc index 11512bcb6..07b66636d 100755 --- a/talk/media/base/mutedvideocapturer.h +++ b/talk/media/webrtc/webrtcvideoframefactory.cc @@ -1,6 +1,6 @@ /* * libjingle - * Copyright 2012 Google Inc. + * 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: @@ -25,36 +25,23 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TALK_MEDIA_BASE_MUTEDVIDEOCAPTURER_H_ -#define TALK_MEDIA_BASE_MUTEDVIDEOCAPTURER_H_ - -#include "webrtc/base/thread.h" -#include "talk/media/base/videocapturer.h" +#include "talk/media/webrtc/webrtcvideoframe.h" +#include "talk/media/webrtc/webrtcvideoframefactory.h" +#include "webrtc/base/logging.h" namespace cricket { -class MutedFramesGenerator; - -class MutedVideoCapturer : public VideoCapturer { - public: - static const char kCapturerId[]; - - MutedVideoCapturer(); - virtual ~MutedVideoCapturer(); - virtual bool GetBestCaptureFormat(const VideoFormat& desired, - VideoFormat* best_format); - virtual CaptureState Start(const VideoFormat& capture_format); - virtual void Stop(); - virtual bool IsRunning(); - virtual bool IsScreencast() const { return false; } - virtual bool GetPreferredFourccs(std::vector* fourccs); - - protected: - void OnMutedFrame(VideoFrame* muted_frame); - - rtc::scoped_ptr frame_generator_; -}; +VideoFrame* WebRtcVideoFrameFactory::CreateAliasedFrame( + const CapturedFrame* aliased_frame, int width, int height) const { + // TODO(pthatcher): Move Alias logic into the VideoFrameFactory and + // out of the VideoFrame. + rtc::scoped_ptr frame(new WebRtcVideoFrame()); + if (!frame->Alias(aliased_frame, width, height)) { + LOG(LS_ERROR) << + "Failed to create WebRtcVideoFrame in CreateAliasedFrame."; + return NULL; + } + return frame.release(); +} } // namespace cricket - -#endif // TALK_MEDIA_BASE_MUTEDVIDEOCAPTURER_H_ diff --git a/talk/media/webrtc/webrtcvideoframefactory.h b/talk/media/webrtc/webrtcvideoframefactory.h new file mode 100755 index 000000000..448cdcf50 --- /dev/null +++ b/talk/media/webrtc/webrtcvideoframefactory.h @@ -0,0 +1,45 @@ +// 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_WEBRTC_WEBRTCVIDEOFRAMEFACTORY_H_ +#define TALK_MEDIA_WEBRTC_WEBRTCVIDEOFRAMEFACTORY_H_ + +#include "talk/media/base/videoframefactory.h" + +namespace cricket { + +struct CapturedFrame; + +// Creates instances of cricket::WebRtcVideoFrame. +class WebRtcVideoFrameFactory : public VideoFrameFactory { + public: + virtual VideoFrame* CreateAliasedFrame( + const CapturedFrame* aliased_frame, int width, int height) const OVERRIDE; +}; + +} // namespace cricket + +#endif // TALK_MEDIA_WEBRTC_WEBRTCVIDEOFRAMEFACTORY_H_