summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormiu <miu@chromium.org>2015-05-15 16:40:03 -0700
committerCommit bot <commit-bot@chromium.org>2015-05-15 23:40:06 +0000
commit39254e67228717bbddf222614abd6cb9ba38608c (patch)
treea008547d23378be8d35f969ede860fde323288a5
parentf54b8097a9c45ed4ad308133d49f05325d6c5070 (diff)
downloadchromium_src-39254e67228717bbddf222614abd6cb9ba38608c.zip
chromium_src-39254e67228717bbddf222614abd6cb9ba38608c.tar.gz
chromium_src-39254e67228717bbddf222614abd6cb9ba38608c.tar.bz2
Implement all resolution change policies for desktop and tab capture.
Prior to this change, desktop/window capture would emit frames of any size, regardless of the resolution change policy; and tab capture would always emit frames of a fixed resolution. Both cause a problem where some receivers of the captured video need to do scaling/letterboxing themselves for proper UX, and other receivers require this be done on by the capture pipeline. This change builds upon https://codereview.chromium.org/1124263004, which allows the client to specify capture constraints that configure the capture pipeline to do (or not do) the scaling/letterboxing. All frame resolution calculation logic has been factored into one place in a new CaptureResolutionChooser class, which also depends on new utility functions added to media/base/video_util.*. BUG=473336 Review URL: https://codereview.chromium.org/1135823004 Cr-Commit-Position: refs/heads/master@{#330232}
-rw-r--r--content/browser/media/capture/capture_resolution_chooser.cc120
-rw-r--r--content/browser/media/capture/capture_resolution_chooser.h59
-rw-r--r--content/browser/media/capture/capture_resolution_chooser_unittest.cc169
-rw-r--r--content/browser/media/capture/content_video_capture_device_core.cc59
-rw-r--r--content/browser/media/capture/content_video_capture_device_core.h14
-rw-r--r--content/browser/media/capture/desktop_capture_device.cc120
-rw-r--r--content/browser/media/capture/desktop_capture_device_unittest.cc140
-rw-r--r--content/browser/media/capture/web_contents_audio_input_stream.cc7
-rw-r--r--content/browser/media/capture/web_contents_audio_input_stream_unittest.cc8
-rw-r--r--content/browser/media/capture/web_contents_tracker.cc44
-rw-r--r--content/browser/media/capture/web_contents_tracker.h30
-rw-r--r--content/browser/media/capture/web_contents_video_capture_device.cc71
-rw-r--r--content/browser/media/capture/web_contents_video_capture_device_unittest.cc258
-rw-r--r--content/content_browser.gypi2
-rw-r--r--content/content_tests.gypi1
-rw-r--r--media/base/video_util.cc49
-rw-r--r--media/base/video_util.h23
-rw-r--r--media/base/video_util_unittest.cc44
18 files changed, 972 insertions, 246 deletions
diff --git a/content/browser/media/capture/capture_resolution_chooser.cc b/content/browser/media/capture/capture_resolution_chooser.cc
new file mode 100644
index 0000000..698882a
--- /dev/null
+++ b/content/browser/media/capture/capture_resolution_chooser.cc
@@ -0,0 +1,120 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/media/capture/capture_resolution_chooser.h"
+
+#include "media/base/limits.h"
+#include "media/base/video_util.h"
+
+namespace content {
+
+namespace {
+
+// Compute the minimum frame size from the given |max_frame_size| and
+// |resolution_change_policy|.
+gfx::Size ComputeMinimumCaptureSize(
+ const gfx::Size& max_frame_size,
+ media::ResolutionChangePolicy resolution_change_policy) {
+ switch (resolution_change_policy) {
+ case media::RESOLUTION_POLICY_FIXED_RESOLUTION:
+ return max_frame_size;
+ case media::RESOLUTION_POLICY_FIXED_ASPECT_RATIO: {
+ // TODO(miu): This is a place-holder until "min constraints" are plumbed-
+ // in from the MediaStream framework. http://crbug.com/473336
+ const int kMinLines = 180;
+ if (max_frame_size.height() <= kMinLines)
+ return max_frame_size;
+ const gfx::Size result(
+ kMinLines * max_frame_size.width() / max_frame_size.height(),
+ kMinLines);
+ if (result.width() <= 0 || result.width() > media::limits::kMaxDimension)
+ return max_frame_size;
+ return result;
+ }
+ case media::RESOLUTION_POLICY_ANY_WITHIN_LIMIT:
+ return gfx::Size(1, 1);
+ case media::RESOLUTION_POLICY_LAST:
+ break;
+ }
+ NOTREACHED();
+ return gfx::Size(1, 1);
+}
+
+// Returns |size|, unless it exceeds |max_size| or is under |min_size|. When
+// the bounds are exceeded, computes and returns an alternate size of similar
+// aspect ratio that is within the bounds.
+gfx::Size ComputeBoundedCaptureSize(const gfx::Size& size,
+ const gfx::Size& min_size,
+ const gfx::Size& max_size) {
+ if (size.width() > max_size.width() || size.height() > max_size.height()) {
+ gfx::Size result = media::ScaleSizeToFitWithinTarget(size, max_size);
+ result.SetToMax(min_size);
+ return result;
+ } else if (size.width() < min_size.width() ||
+ size.height() < min_size.height()) {
+ gfx::Size result = media::ScaleSizeToEncompassTarget(size, min_size);
+ result.SetToMin(max_size);
+ return result;
+ } else {
+ return size;
+ }
+}
+
+} // namespace
+
+CaptureResolutionChooser::CaptureResolutionChooser(
+ const gfx::Size& max_frame_size,
+ media::ResolutionChangePolicy resolution_change_policy)
+ : max_frame_size_(max_frame_size),
+ min_frame_size_(ComputeMinimumCaptureSize(max_frame_size,
+ resolution_change_policy)),
+ resolution_change_policy_(resolution_change_policy),
+ constrained_size_(max_frame_size) {
+ DCHECK_LT(0, max_frame_size_.width());
+ DCHECK_LT(0, max_frame_size_.height());
+ DCHECK_LE(min_frame_size_.width(), max_frame_size_.width());
+ DCHECK_LE(min_frame_size_.height(), max_frame_size_.height());
+
+ RecomputeCaptureSize();
+}
+
+CaptureResolutionChooser::~CaptureResolutionChooser() {}
+
+void CaptureResolutionChooser::SetSourceSize(const gfx::Size& source_size) {
+ if (source_size.IsEmpty())
+ return;
+
+ switch (resolution_change_policy_) {
+ case media::RESOLUTION_POLICY_FIXED_RESOLUTION:
+ // Source size changes do not affect the frame resolution. Frame
+ // resolution is always fixed to |max_frame_size_|.
+ break;
+
+ case media::RESOLUTION_POLICY_FIXED_ASPECT_RATIO:
+ constrained_size_ = ComputeBoundedCaptureSize(
+ media::PadToMatchAspectRatio(source_size, max_frame_size_),
+ min_frame_size_,
+ max_frame_size_);
+ RecomputeCaptureSize();
+ break;
+
+ case media::RESOLUTION_POLICY_ANY_WITHIN_LIMIT:
+ constrained_size_ = ComputeBoundedCaptureSize(
+ source_size, min_frame_size_, max_frame_size_);
+ RecomputeCaptureSize();
+ break;
+
+ case media::RESOLUTION_POLICY_LAST:
+ NOTREACHED();
+ }
+}
+
+void CaptureResolutionChooser::RecomputeCaptureSize() {
+ // TODO(miu): An upcoming change will introduce the ability to find the best
+ // capture resolution, given the current capabilities of the system.
+ // http://crbug.com/156767
+ capture_size_ = constrained_size_;
+}
+
+} // namespace content
diff --git a/content/browser/media/capture/capture_resolution_chooser.h b/content/browser/media/capture/capture_resolution_chooser.h
new file mode 100644
index 0000000..f1bda79
--- /dev/null
+++ b/content/browser/media/capture/capture_resolution_chooser.h
@@ -0,0 +1,59 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_MEDIA_CAPTURE_CAPTURE_RESOLUTION_CHOOSER_H_
+#define CONTENT_BROWSER_MEDIA_CAPTURE_CAPTURE_RESOLUTION_CHOOSER_H_
+
+#include "content/common/content_export.h"
+#include "media/base/video_capture_types.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace content {
+
+// Encapsulates the logic that determines the capture frame resolution based on:
+// 1. The configured maximum frame resolution and resolution change policy.
+// 2. The resolution of the source content.
+// 3. The current capabilities of the end-to-end system, in terms of the
+// maximum number of pixels per frame.
+class CONTENT_EXPORT CaptureResolutionChooser {
+ public:
+ // media::ResolutionChangePolicy determines whether the variable frame
+ // resolutions being computed must adhere to a fixed aspect ratio or not, or
+ // that there must only be a single fixed resolution.
+ CaptureResolutionChooser(
+ const gfx::Size& max_frame_size,
+ media::ResolutionChangePolicy resolution_change_policy);
+ ~CaptureResolutionChooser();
+
+ // Returns the current capture frame resolution to use.
+ gfx::Size capture_size() const {
+ return capture_size_;
+ }
+
+ // Updates the capture size based on a change in the resolution of the source
+ // content.
+ void SetSourceSize(const gfx::Size& source_size);
+
+ private:
+ // Called after any update that requires |capture_size_| be re-computed.
+ void RecomputeCaptureSize();
+
+ // Hard constraints.
+ const gfx::Size max_frame_size_;
+ const gfx::Size min_frame_size_; // Computed from the ctor arguments.
+
+ // Specifies the set of heuristics to use.
+ const media::ResolutionChangePolicy resolution_change_policy_;
+
+ // The capture frame resolution to use, ignoring the limitations imposed by
+ // the capability metric.
+ gfx::Size constrained_size_;
+
+ // The current computed capture frame resolution.
+ gfx::Size capture_size_;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_MEDIA_CAPTURE_RESOLUTION_CHOOSER_H_
diff --git a/content/browser/media/capture/capture_resolution_chooser_unittest.cc b/content/browser/media/capture/capture_resolution_chooser_unittest.cc
new file mode 100644
index 0000000..d91c0ce
--- /dev/null
+++ b/content/browser/media/capture/capture_resolution_chooser_unittest.cc
@@ -0,0 +1,169 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/media/capture/capture_resolution_chooser.h"
+
+#include "base/location.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using tracked_objects::Location;
+
+namespace content {
+
+namespace {
+
+// 16:9 maximum and minimum frame sizes.
+const int kMaxFrameWidth = 3840;
+const int kMaxFrameHeight = 2160;
+const int kMinFrameWidth = 320;
+const int kMinFrameHeight = 180;
+
+// Checks whether |size| is strictly between (inclusive) |min_size| and
+// |max_size| and has the same aspect ratio as |max_size|.
+void ExpectIsWithinBoundsAndSameAspectRatio(const Location& location,
+ const gfx::Size& min_size,
+ const gfx::Size& max_size,
+ const gfx::Size& size) {
+ SCOPED_TRACE(::testing::Message() << "From here: " << location.ToString());
+ EXPECT_LE(min_size.width(), size.width());
+ EXPECT_LE(min_size.height(), size.height());
+ EXPECT_GE(max_size.width(), size.width());
+ EXPECT_GE(max_size.height(), size.height());
+ EXPECT_NEAR(static_cast<double>(max_size.width()) / max_size.height(),
+ static_cast<double>(size.width()) / size.height(),
+ 0.01);
+}
+
+} // namespace
+
+TEST(CaptureResolutionChooserTest,
+ FixedResolutionPolicy_CaptureSizeAlwaysFixed) {
+ const gfx::Size the_one_frame_size(kMaxFrameWidth, kMaxFrameHeight);
+ CaptureResolutionChooser chooser(the_one_frame_size,
+ media::RESOLUTION_POLICY_FIXED_RESOLUTION);
+ EXPECT_EQ(the_one_frame_size, chooser.capture_size());
+
+ chooser.SetSourceSize(the_one_frame_size);
+ EXPECT_EQ(the_one_frame_size, chooser.capture_size());
+
+ chooser.SetSourceSize(gfx::Size(kMaxFrameWidth + 424, kMaxFrameHeight - 101));
+ EXPECT_EQ(the_one_frame_size, chooser.capture_size());
+
+ chooser.SetSourceSize(gfx::Size(kMaxFrameWidth - 202, kMaxFrameHeight + 56));
+ EXPECT_EQ(the_one_frame_size, chooser.capture_size());
+
+ chooser.SetSourceSize(gfx::Size(kMinFrameWidth, kMinFrameHeight));
+ EXPECT_EQ(the_one_frame_size, chooser.capture_size());
+}
+
+TEST(CaptureResolutionChooserTest,
+ FixedAspectRatioPolicy_CaptureSizeHasSameAspectRatio) {
+ CaptureResolutionChooser chooser(
+ gfx::Size(kMaxFrameWidth, kMaxFrameHeight),
+ media::RESOLUTION_POLICY_FIXED_ASPECT_RATIO);
+
+ // Starting condition.
+ const gfx::Size min_size(kMinFrameWidth, kMinFrameHeight);
+ const gfx::Size max_size(kMaxFrameWidth, kMaxFrameHeight);
+ ExpectIsWithinBoundsAndSameAspectRatio(
+ FROM_HERE, min_size, max_size, chooser.capture_size());
+
+ // Max size in --> max size out.
+ chooser.SetSourceSize(gfx::Size(kMaxFrameWidth, kMaxFrameHeight));
+ ExpectIsWithinBoundsAndSameAspectRatio(
+ FROM_HERE, min_size, max_size, chooser.capture_size());
+
+ // Various source sizes within bounds.
+ chooser.SetSourceSize(gfx::Size(640, 480));
+ ExpectIsWithinBoundsAndSameAspectRatio(
+ FROM_HERE, min_size, max_size, chooser.capture_size());
+
+ chooser.SetSourceSize(gfx::Size(480, 640));
+ ExpectIsWithinBoundsAndSameAspectRatio(
+ FROM_HERE, min_size, max_size, chooser.capture_size());
+
+ chooser.SetSourceSize(gfx::Size(640, 640));
+ ExpectIsWithinBoundsAndSameAspectRatio(
+ FROM_HERE, min_size, max_size, chooser.capture_size());
+
+ // Bad source size results in no update.
+ const gfx::Size unchanged_size = chooser.capture_size();
+ chooser.SetSourceSize(gfx::Size(0, 0));
+ EXPECT_EQ(unchanged_size, chooser.capture_size());
+
+ // Downscaling size (preserving aspect ratio) when source size exceeds the
+ // upper bounds.
+ chooser.SetSourceSize(gfx::Size(kMaxFrameWidth * 2, kMaxFrameHeight * 2));
+ ExpectIsWithinBoundsAndSameAspectRatio(
+ FROM_HERE, min_size, max_size, chooser.capture_size());
+
+ chooser.SetSourceSize(gfx::Size(kMaxFrameWidth * 2, kMaxFrameHeight));
+ ExpectIsWithinBoundsAndSameAspectRatio(
+ FROM_HERE, min_size, max_size, chooser.capture_size());
+
+ chooser.SetSourceSize(gfx::Size(kMaxFrameWidth, kMaxFrameHeight * 2));
+ ExpectIsWithinBoundsAndSameAspectRatio(
+ FROM_HERE, min_size, max_size, chooser.capture_size());
+
+ // Upscaling size (preserving aspect ratio) when source size is under the
+ // lower bounds.
+ chooser.SetSourceSize(gfx::Size(kMinFrameWidth / 2, kMinFrameHeight / 2));
+ ExpectIsWithinBoundsAndSameAspectRatio(
+ FROM_HERE, min_size, max_size, chooser.capture_size());
+
+ chooser.SetSourceSize(gfx::Size(kMinFrameWidth / 2, kMaxFrameHeight));
+ ExpectIsWithinBoundsAndSameAspectRatio(
+ FROM_HERE, min_size, max_size, chooser.capture_size());
+
+ chooser.SetSourceSize(gfx::Size(kMinFrameWidth, kMinFrameHeight / 2));
+ ExpectIsWithinBoundsAndSameAspectRatio(
+ FROM_HERE, min_size, max_size, chooser.capture_size());
+}
+
+TEST(CaptureResolutionChooserTest,
+ AnyWithinLimitPolicy_CaptureSizeIsAnythingWithinLimits) {
+ const gfx::Size max_size(kMaxFrameWidth, kMaxFrameHeight);
+ CaptureResolutionChooser chooser(
+ max_size, media::RESOLUTION_POLICY_ANY_WITHIN_LIMIT);
+
+ // Starting condition.
+ EXPECT_EQ(max_size, chooser.capture_size());
+
+ // Max size in --> max size out.
+ chooser.SetSourceSize(max_size);
+ EXPECT_EQ(max_size, chooser.capture_size());
+
+ // Various source sizes within bounds.
+ chooser.SetSourceSize(gfx::Size(640, 480));
+ EXPECT_EQ(gfx::Size(640, 480), chooser.capture_size());
+
+ chooser.SetSourceSize(gfx::Size(480, 640));
+ EXPECT_EQ(gfx::Size(480, 640), chooser.capture_size());
+
+ chooser.SetSourceSize(gfx::Size(640, 640));
+ EXPECT_EQ(gfx::Size(640, 640), chooser.capture_size());
+
+ chooser.SetSourceSize(gfx::Size(2, 2));
+ EXPECT_EQ(gfx::Size(2, 2), chooser.capture_size());
+
+ // Bad source size results in no update.
+ const gfx::Size unchanged_size = chooser.capture_size();
+ chooser.SetSourceSize(gfx::Size(0, 0));
+ EXPECT_EQ(unchanged_size, chooser.capture_size());
+
+ // Downscaling size (preserving aspect ratio) when source size exceeds the
+ // upper bounds.
+ chooser.SetSourceSize(gfx::Size(kMaxFrameWidth * 2, kMaxFrameHeight * 2));
+ EXPECT_EQ(max_size, chooser.capture_size());
+
+ chooser.SetSourceSize(gfx::Size(kMaxFrameWidth * 2, kMaxFrameHeight));
+ EXPECT_EQ(gfx::Size(kMaxFrameWidth, kMaxFrameHeight / 2),
+ chooser.capture_size());
+
+ chooser.SetSourceSize(gfx::Size(kMaxFrameWidth, kMaxFrameHeight * 2));
+ EXPECT_EQ(gfx::Size(kMaxFrameWidth / 2, kMaxFrameHeight),
+ chooser.capture_size());
+}
+
+} // namespace content
diff --git a/content/browser/media/capture/content_video_capture_device_core.cc b/content/browser/media/capture/content_video_capture_device_core.cc
index 8d44104..da0862c 100644
--- a/content/browser/media/capture/content_video_capture_device_core.cc
+++ b/content/browser/media/capture/content_video_capture_device_core.cc
@@ -49,7 +49,9 @@ ThreadSafeCaptureOracle::ThreadSafeCaptureOracle(
oracle_(base::TimeDelta::FromMicroseconds(
static_cast<int64>(1000000.0 / params.requested_format.frame_rate +
0.5 /* to round to nearest int */))),
- params_(params) {}
+ params_(params),
+ resolution_chooser_(params.requested_format.frame_size,
+ params.resolution_change_policy) {}
ThreadSafeCaptureOracle::~ThreadSafeCaptureOracle() {}
@@ -67,9 +69,7 @@ bool ThreadSafeCaptureOracle::ObserveEventAndDecideCapture(
if (!client_)
return false; // Capture is stopped.
- if (capture_size_.IsEmpty())
- capture_size_ = max_frame_size();
- const gfx::Size visible_size = capture_size_;
+ const gfx::Size visible_size = resolution_chooser_.capture_size();
// Always round up the coded size to multiple of 16 pixels.
// See http://crbug.com/402151.
const gfx::Size coded_size((visible_size.width() + 15) & ~15,
@@ -141,30 +141,15 @@ bool ThreadSafeCaptureOracle::ObserveEventAndDecideCapture(
gfx::Size ThreadSafeCaptureOracle::GetCaptureSize() const {
base::AutoLock guard(lock_);
- return capture_size_.IsEmpty() ? max_frame_size() : capture_size_;
+ return resolution_chooser_.capture_size();
}
void ThreadSafeCaptureOracle::UpdateCaptureSize(const gfx::Size& source_size) {
base::AutoLock guard(lock_);
-
- // Update |capture_size_| based on |source_size| if either: 1) The resolution
- // change policy specifies fixed frame sizes and |capture_size_| has not yet
- // been set; or 2) The resolution change policy specifies dynamic frame
- // sizes.
- if (capture_size_.IsEmpty() || params_.resolution_change_policy ==
- media::RESOLUTION_POLICY_ANY_WITHIN_LIMIT) {
- capture_size_ = source_size;
- // The capture size should not exceed the maximum frame size.
- if (capture_size_.width() > max_frame_size().width() ||
- capture_size_.height() > max_frame_size().height()) {
- capture_size_ = media::ComputeLetterboxRegion(
- gfx::Rect(max_frame_size()), capture_size_).size();
- }
- // The capture size must be even and not less than the minimum frame size.
- capture_size_ = gfx::Size(
- std::max(kMinFrameWidth, MakeEven(capture_size_.width())),
- std::max(kMinFrameHeight, MakeEven(capture_size_.height())));
- }
+ resolution_chooser_.SetSourceSize(source_size);
+ VLOG(1) << "Source size changed to " << source_size.ToString()
+ << " --> Capture size is now "
+ << resolution_chooser_.capture_size().ToString();
}
void ThreadSafeCaptureOracle::Stop() {
@@ -234,23 +219,15 @@ void ContentVideoCaptureDeviceCore::AllocateAndStart(
return;
}
- if (params.requested_format.frame_size.width() < kMinFrameWidth ||
- params.requested_format.frame_size.height() < kMinFrameHeight) {
- std::string error_msg =
- "invalid frame size: " + params.requested_format.frame_size.ToString();
- DVLOG(1) << error_msg;
- client->OnError(error_msg);
- return;
- }
-
- media::VideoCaptureParams new_params = params;
- // Frame dimensions must each be an even integer since the client wants (or
- // will convert to) YUV420.
- new_params.requested_format.frame_size.SetSize(
- MakeEven(params.requested_format.frame_size.width()),
- MakeEven(params.requested_format.frame_size.height()));
+ if (params.requested_format.frame_size.IsEmpty()) {
+ std::string error_msg =
+ "invalid frame size: " + params.requested_format.frame_size.ToString();
+ DVLOG(1) << error_msg;
+ client->OnError(error_msg);
+ return;
+ }
- oracle_proxy_ = new ThreadSafeCaptureOracle(client.Pass(), new_params);
+ oracle_proxy_ = new ThreadSafeCaptureOracle(client.Pass(), params);
// Starts the capture machine asynchronously.
BrowserThread::PostTaskAndReplyWithResult(
@@ -259,7 +236,7 @@ void ContentVideoCaptureDeviceCore::AllocateAndStart(
base::Bind(&VideoCaptureMachine::Start,
base::Unretained(capture_machine_.get()),
oracle_proxy_,
- new_params),
+ params),
base::Bind(&ContentVideoCaptureDeviceCore::CaptureStarted, AsWeakPtr()));
TransitionStateTo(kCapturing);
diff --git a/content/browser/media/capture/content_video_capture_device_core.h b/content/browser/media/capture/content_video_capture_device_core.h
index f477a15..67dffd6 100644
--- a/content/browser/media/capture/content_video_capture_device_core.h
+++ b/content/browser/media/capture/content_video_capture_device_core.h
@@ -11,6 +11,7 @@
#include "base/memory/weak_ptr.h"
#include "base/threading/thread.h"
#include "base/threading/thread_checker.h"
+#include "content/browser/media/capture/capture_resolution_chooser.h"
#include "content/browser/media/capture/video_capture_oracle.h"
#include "content/common/content_export.h"
#include "media/base/video_frame.h"
@@ -23,15 +24,6 @@ class VideoFrame;
namespace content {
-const int kMinFrameWidth = 2;
-const int kMinFrameHeight = 2;
-
-// Returns the nearest even integer closer to zero.
-template<typename IntType>
-IntType MakeEven(IntType x) {
- return x & static_cast<IntType>(-2);
-}
-
class VideoCaptureMachine;
// Thread-safe, refcounted proxy to the VideoCaptureOracle. This proxy wraps
@@ -104,8 +96,8 @@ class ThreadSafeCaptureOracle
// The video capture parameters used to construct the oracle proxy.
const media::VideoCaptureParams params_;
- // The current video capture size.
- gfx::Size capture_size_;
+ // Determines video capture frame sizes.
+ CaptureResolutionChooser resolution_chooser_;
};
// Keeps track of the video capture source frames and executes copying on the
diff --git a/content/browser/media/capture/desktop_capture_device.cc b/content/browser/media/capture/desktop_capture_device.cc
index a11e473..e42a678 100644
--- a/content/browser/media/capture/desktop_capture_device.cc
+++ b/content/browser/media/capture/desktop_capture_device.cc
@@ -12,6 +12,7 @@
#include "base/synchronization/lock.h"
#include "base/threading/thread.h"
#include "base/timer/timer.h"
+#include "content/browser/media/capture/capture_resolution_chooser.h"
#include "content/browser/media/capture/desktop_capture_device_uma_types.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/desktop_media_id.h"
@@ -72,11 +73,6 @@ class DesktopCaptureDevice::Core : public webrtc::DesktopCapturer::Callback {
webrtc::SharedMemory* CreateSharedMemory(size_t size) override;
void OnCaptureCompleted(webrtc::DesktopFrame* frame) override;
- // Chooses new output properties based on the supplied source size and the
- // properties requested to Allocate(), and dispatches OnFrameInfo[Changed]
- // notifications.
- void RefreshCaptureFormat(const webrtc::DesktopSize& frame_size);
-
// Method that is scheduled on |task_runner_| to be called on regular interval
// to capture a frame.
void OnCaptureTimer();
@@ -97,24 +93,20 @@ class DesktopCaptureDevice::Core : public webrtc::DesktopCapturer::Callback {
// on the task_runner_ thread.
scoped_ptr<Client> client_;
- // Requested video capture format (width, height, frame rate, etc).
- media::VideoCaptureParams requested_params_;
-
- // Actual video capture format being generated.
- media::VideoCaptureFormat capture_format_;
+ // Requested video capture frame rate.
+ float requested_frame_rate_;
// Size of frame most recently captured from the source.
webrtc::DesktopSize previous_frame_size_;
+ // Determines the size of frames to deliver to the |client_|.
+ scoped_ptr<CaptureResolutionChooser> resolution_chooser_;
+
// DesktopFrame into which captured frames are down-scaled and/or letterboxed,
// depending upon the caller's requested capture capabilities. If frames can
// be returned to the caller directly then this is NULL.
scoped_ptr<webrtc::DesktopFrame> output_frame_;
- // Sub-rectangle of |output_frame_| into which the source will be scaled
- // and/or letterboxed.
- webrtc::DesktopRect output_rect_;
-
// Timer used to capture the frame.
base::OneShotTimer<Core> capture_timer_;
@@ -167,12 +159,10 @@ void DesktopCaptureDevice::Core::AllocateAndStart(
DCHECK(!client_.get());
client_ = client.Pass();
- requested_params_ = params;
-
- capture_format_ = requested_params_.requested_format;
-
- // This capturer always outputs ARGB, non-interlaced.
- capture_format_.pixel_format = media::PIXEL_FORMAT_ARGB;
+ requested_frame_rate_ = params.requested_format.frame_rate;
+ resolution_chooser_.reset(new CaptureResolutionChooser(
+ params.requested_format.frame_size,
+ params.resolution_change_policy));
power_save_blocker_.reset(
PowerSaveBlocker::Create(
@@ -238,16 +228,29 @@ void DesktopCaptureDevice::Core::OnCaptureCompleted(
scoped_ptr<webrtc::DesktopFrame> owned_frame(frame);
+ // If the frame size has changed, drop the output frame (if any), and
+ // determine the new output size.
+ if (!previous_frame_size_.equals(frame->size())) {
+ output_frame_.reset();
+ resolution_chooser_->SetSourceSize(gfx::Size(frame->size().width(),
+ frame->size().height()));
+ previous_frame_size_ = frame->size();
+ }
+ // Align to 2x2 pixel boundaries, as required by OnIncomingCapturedData() so
+ // it can convert the frame to I420 format.
+ const webrtc::DesktopSize output_size(
+ resolution_chooser_->capture_size().width() & ~1,
+ resolution_chooser_->capture_size().height() & ~1);
+ if (output_size.is_empty())
+ return;
+
// On OSX We receive a 1x1 frame when the shared window is minimized. It
// cannot be subsampled to I420 and will be dropped downstream. So we replace
// it with a black frame to avoid the video appearing frozen at the last
// frame.
if (frame->size().width() == 1 || frame->size().height() == 1) {
if (!black_frame_.get()) {
- black_frame_.reset(
- new webrtc::BasicDesktopFrame(
- webrtc::DesktopSize(capture_format_.frame_size.width(),
- capture_format_.frame_size.height())));
+ black_frame_.reset(new webrtc::BasicDesktopFrame(output_size));
memset(black_frame_->data(),
0,
black_frame_->stride() * black_frame_->size().height());
@@ -256,11 +259,6 @@ void DesktopCaptureDevice::Core::OnCaptureCompleted(
frame = black_frame_.get();
}
- // Handle initial frame size and size changes.
- RefreshCaptureFormat(frame->size());
-
- webrtc::DesktopSize output_size(capture_format_.frame_size.width(),
- capture_format_.frame_size.height());
size_t output_bytes = output_size.width() * output_size.height() *
webrtc::DesktopFrame::kBytesPerPixel;
const uint8_t* output_data = NULL;
@@ -270,7 +268,7 @@ void DesktopCaptureDevice::Core::OnCaptureCompleted(
// match the output size.
// Allocate a buffer of the correct size to scale the frame into.
- // |output_frame_| is cleared whenever |output_rect_| changes, so we don't
+ // |output_frame_| is cleared whenever the output size changes, so we don't
// need to worry about clearing out stale pixel data in letterboxed areas.
if (!output_frame_) {
output_frame_.reset(new webrtc::BasicDesktopFrame(output_size));
@@ -280,13 +278,15 @@ void DesktopCaptureDevice::Core::OnCaptureCompleted(
// TODO(wez): Optimize this to scale only changed portions of the output,
// using ARGBScaleClip().
+ const webrtc::DesktopRect output_rect =
+ ComputeLetterboxRect(output_size, frame->size());
uint8_t* output_rect_data = output_frame_->data() +
- output_frame_->stride() * output_rect_.top() +
- webrtc::DesktopFrame::kBytesPerPixel * output_rect_.left();
+ output_frame_->stride() * output_rect.top() +
+ webrtc::DesktopFrame::kBytesPerPixel * output_rect.left();
libyuv::ARGBScale(frame->data(), frame->stride(),
frame->size().width(), frame->size().height(),
output_rect_data, output_frame_->stride(),
- output_rect_.width(), output_rect_.height(),
+ output_rect.width(), output_rect.height(),
libyuv::kFilterBilinear);
output_data = output_frame_->data();
} else if (IsFrameUnpackedOrInverted(frame)) {
@@ -311,49 +311,14 @@ void DesktopCaptureDevice::Core::OnCaptureCompleted(
}
client_->OnIncomingCapturedData(
- output_data, output_bytes, capture_format_, 0, base::TimeTicks::Now());
-}
-
-void DesktopCaptureDevice::Core::RefreshCaptureFormat(
- const webrtc::DesktopSize& frame_size) {
- if (previous_frame_size_.equals(frame_size))
- return;
-
- // Clear the output frame, if any, since it will either need resizing, or
- // clearing of stale data in letterbox areas, anyway.
- output_frame_.reset();
-
- if (previous_frame_size_.is_empty() ||
- requested_params_.resolution_change_policy ==
- media::RESOLUTION_POLICY_ANY_WITHIN_LIMIT) {
- // If this is the first frame, or the receiver supports variable resolution
- // then determine the output size by treating the requested width & height
- // as maxima.
- if (frame_size.width() >
- requested_params_.requested_format.frame_size.width() ||
- frame_size.height() >
- requested_params_.requested_format.frame_size.height()) {
- output_rect_ = ComputeLetterboxRect(
- webrtc::DesktopSize(
- requested_params_.requested_format.frame_size.width(),
- requested_params_.requested_format.frame_size.height()),
- frame_size);
- output_rect_.Translate(-output_rect_.left(), -output_rect_.top());
- } else {
- output_rect_ = webrtc::DesktopRect::MakeSize(frame_size);
- }
- capture_format_.frame_size.SetSize(output_rect_.width(),
- output_rect_.height());
- } else {
- // Otherwise the output frame size cannot change, so just scale and
- // letterbox.
- output_rect_ = ComputeLetterboxRect(
- webrtc::DesktopSize(capture_format_.frame_size.width(),
- capture_format_.frame_size.height()),
- frame_size);
- }
-
- previous_frame_size_ = frame_size;
+ output_data,
+ output_bytes,
+ media::VideoCaptureFormat(gfx::Size(output_size.width(),
+ output_size.height()),
+ requested_frame_rate_,
+ media::PIXEL_FORMAT_ARGB),
+ 0,
+ base::TimeTicks::Now());
}
void DesktopCaptureDevice::Core::OnCaptureTimer() {
@@ -375,7 +340,8 @@ void DesktopCaptureDevice::Core::CaptureFrameAndScheduleNext() {
// Limit frame-rate to reduce CPU consumption.
base::TimeDelta capture_period = std::max(
(last_capture_duration * 100) / kMaximumCpuConsumptionPercentage,
- base::TimeDelta::FromSeconds(1) / capture_format_.frame_rate);
+ base::TimeDelta::FromMicroseconds(static_cast<int64>(
+ 1000000.0 / requested_frame_rate_ + 0.5 /* round to nearest int */)));
// Schedule a task for the next frame.
capture_timer_.Start(FROM_HERE, capture_period - last_capture_duration,
diff --git a/content/browser/media/capture/desktop_capture_device_unittest.cc b/content/browser/media/capture/desktop_capture_device_unittest.cc
index c86ddd3..56e06ff 100644
--- a/content/browser/media/capture/desktop_capture_device_unittest.cc
+++ b/content/browser/media/capture/desktop_capture_device_unittest.cc
@@ -4,6 +4,7 @@
#include "content/browser/media/capture/desktop_capture_device.h"
+#include <algorithm>
#include <string>
#include "base/basictypes.h"
@@ -22,8 +23,10 @@ using ::testing::_;
using ::testing::AnyNumber;
using ::testing::DoAll;
using ::testing::Expectation;
+using ::testing::Invoke;
using ::testing::InvokeWithoutArgs;
using ::testing::SaveArg;
+using ::testing::WithArg;
namespace content {
@@ -33,10 +36,10 @@ MATCHER_P2(EqualsCaptureCapability, width, height, "") {
return arg.width == width && arg.height == height;
}
-const int kTestFrameWidth1 = 100;
-const int kTestFrameHeight1 = 100;
-const int kTestFrameWidth2 = 200;
-const int kTestFrameHeight2 = 150;
+const int kTestFrameWidth1 = 500;
+const int kTestFrameHeight1 = 500;
+const int kTestFrameWidth2 = 400;
+const int kTestFrameHeight2 = 300;
const int kFrameRate = 30;
@@ -208,6 +211,32 @@ class FakeScreenCapturer : public webrtc::ScreenCapturer {
bool generate_cropped_frames_;
};
+// Helper used to check that only two specific frame sizes are delivered to the
+// OnIncomingCapturedData() callback.
+class FormatChecker {
+ public:
+ FormatChecker(const gfx::Size& size_for_even_frames,
+ const gfx::Size& size_for_odd_frames)
+ : size_for_even_frames_(size_for_even_frames),
+ size_for_odd_frames_(size_for_odd_frames),
+ frame_count_(0) {}
+
+ void ExpectAcceptableSize(const media::VideoCaptureFormat& format) {
+ if (frame_count_ % 2 == 0)
+ EXPECT_EQ(size_for_even_frames_, format.frame_size);
+ else
+ EXPECT_EQ(size_for_odd_frames_, format.frame_size);
+ ++frame_count_;
+ EXPECT_EQ(kFrameRate, format.frame_rate);
+ EXPECT_EQ(media::PIXEL_FORMAT_ARGB, format.pixel_format);
+ }
+
+ private:
+ const gfx::Size size_for_even_frames_;
+ const gfx::Size size_for_odd_frames_;
+ int frame_count_;
+};
+
} // namespace
class DesktopCaptureDeviceTest : public testing::Test {
@@ -277,15 +306,15 @@ TEST_F(DesktopCaptureDeviceTest, ScreenResolutionChangeConstantResolution) {
CreateScreenCaptureDevice(scoped_ptr<webrtc::DesktopCapturer>(mock_capturer));
- media::VideoCaptureFormat format;
+ FormatChecker format_checker(gfx::Size(kTestFrameWidth1, kTestFrameHeight1),
+ gfx::Size(kTestFrameWidth1, kTestFrameHeight1));
base::WaitableEvent done_event(false, false);
- int frame_size;
scoped_ptr<MockDeviceClient> client(new MockDeviceClient());
EXPECT_CALL(*client, OnError(_)).Times(0);
EXPECT_CALL(*client, OnIncomingCapturedData(_, _, _, _, _)).WillRepeatedly(
- DoAll(SaveArg<1>(&frame_size),
- SaveArg<2>(&format),
+ DoAll(WithArg<2>(Invoke(&format_checker,
+ &FormatChecker::ExpectAcceptableSize)),
InvokeWithoutArgs(&done_event, &base::WaitableEvent::Signal)));
media::VideoCaptureParams capture_params;
@@ -293,23 +322,66 @@ TEST_F(DesktopCaptureDeviceTest, ScreenResolutionChangeConstantResolution) {
kTestFrameHeight1);
capture_params.requested_format.frame_rate = kFrameRate;
capture_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420;
+ capture_params.resolution_change_policy =
+ media::RESOLUTION_POLICY_FIXED_RESOLUTION;
capture_device_->AllocateAndStart(capture_params, client.Pass());
// Capture at least two frames, to ensure that the source frame size has
- // changed while capturing.
- EXPECT_TRUE(done_event.TimedWait(TestTimeouts::action_max_timeout()));
- done_event.Reset();
- EXPECT_TRUE(done_event.TimedWait(TestTimeouts::action_max_timeout()));
+ // changed to two different sizes while capturing. The mock for
+ // OnIncomingCapturedData() will use FormatChecker to examine the format of
+ // each frame being delivered.
+ for (int i = 0; i < 2; ++i) {
+ EXPECT_TRUE(done_event.TimedWait(TestTimeouts::action_max_timeout()));
+ done_event.Reset();
+ }
capture_device_->StopAndDeAllocate();
+}
- EXPECT_EQ(kTestFrameWidth1, format.frame_size.width());
- EXPECT_EQ(kTestFrameHeight1, format.frame_size.height());
- EXPECT_EQ(kFrameRate, format.frame_rate);
- EXPECT_EQ(media::PIXEL_FORMAT_ARGB, format.pixel_format);
+// Test that screen capturer behaves correctly if the source frame size changes,
+// where the video frames sent the the client vary in resolution but maintain
+// the same aspect ratio.
+TEST_F(DesktopCaptureDeviceTest, ScreenResolutionChangeFixedAspectRatio) {
+ FakeScreenCapturer* mock_capturer = new FakeScreenCapturer();
- EXPECT_EQ(format.frame_size.GetArea() * 4, frame_size);
+ CreateScreenCaptureDevice(scoped_ptr<webrtc::DesktopCapturer>(mock_capturer));
+
+ FormatChecker format_checker(gfx::Size(888, 500), gfx::Size(532, 300));
+ base::WaitableEvent done_event(false, false);
+
+ scoped_ptr<MockDeviceClient> client(new MockDeviceClient());
+ EXPECT_CALL(*client, OnError(_)).Times(0);
+ EXPECT_CALL(*client, OnIncomingCapturedData(_, _, _, _, _)).WillRepeatedly(
+ DoAll(WithArg<2>(Invoke(&format_checker,
+ &FormatChecker::ExpectAcceptableSize)),
+ InvokeWithoutArgs(&done_event, &base::WaitableEvent::Signal)));
+
+ media::VideoCaptureParams capture_params;
+ const gfx::Size high_def_16_by_9(1920, 1080);
+ ASSERT_GE(high_def_16_by_9.width(),
+ std::max(kTestFrameWidth1, kTestFrameWidth2));
+ ASSERT_GE(high_def_16_by_9.height(),
+ std::max(kTestFrameHeight1, kTestFrameHeight2));
+ capture_params.requested_format.frame_size = high_def_16_by_9;
+ capture_params.requested_format.frame_rate = kFrameRate;
+ capture_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420;
+ capture_params.resolution_change_policy =
+ media::RESOLUTION_POLICY_FIXED_ASPECT_RATIO;
+
+ capture_device_->AllocateAndStart(
+ capture_params, client.Pass());
+
+ // Capture at least three frames, to ensure that the source frame size has
+ // changed to two different sizes while capturing. The mock for
+ // OnIncomingCapturedData() will use FormatChecker to examine the format of
+ // each frame being delivered.
+ for (int i = 0; i < 3; ++i) {
+ EXPECT_TRUE(done_event.TimedWait(TestTimeouts::action_max_timeout()));
+ done_event.Reset();
+ }
+
+ capture_device_->StopAndDeAllocate();
}
// Test that screen capturer behaves correctly if the source frame size changes
@@ -319,38 +391,42 @@ TEST_F(DesktopCaptureDeviceTest, ScreenResolutionChangeVariableResolution) {
CreateScreenCaptureDevice(scoped_ptr<webrtc::DesktopCapturer>(mock_capturer));
- media::VideoCaptureFormat format;
+ FormatChecker format_checker(gfx::Size(kTestFrameWidth1, kTestFrameHeight1),
+ gfx::Size(kTestFrameWidth2, kTestFrameHeight2));
base::WaitableEvent done_event(false, false);
scoped_ptr<MockDeviceClient> client(new MockDeviceClient());
EXPECT_CALL(*client, OnError(_)).Times(0);
EXPECT_CALL(*client, OnIncomingCapturedData(_, _, _, _, _)).WillRepeatedly(
- DoAll(SaveArg<2>(&format),
+ DoAll(WithArg<2>(Invoke(&format_checker,
+ &FormatChecker::ExpectAcceptableSize)),
InvokeWithoutArgs(&done_event, &base::WaitableEvent::Signal)));
media::VideoCaptureParams capture_params;
- capture_params.requested_format.frame_size.SetSize(kTestFrameWidth2,
- kTestFrameHeight2);
+ const gfx::Size high_def_16_by_9(1920, 1080);
+ ASSERT_GE(high_def_16_by_9.width(),
+ std::max(kTestFrameWidth1, kTestFrameWidth2));
+ ASSERT_GE(high_def_16_by_9.height(),
+ std::max(kTestFrameHeight1, kTestFrameHeight2));
+ capture_params.requested_format.frame_size = high_def_16_by_9;
capture_params.requested_format.frame_rate = kFrameRate;
capture_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420;
+ capture_params.resolution_change_policy =
+ media::RESOLUTION_POLICY_ANY_WITHIN_LIMIT;
capture_device_->AllocateAndStart(
capture_params, client.Pass());
// Capture at least three frames, to ensure that the source frame size has
- // changed at least twice while capturing.
- EXPECT_TRUE(done_event.TimedWait(TestTimeouts::action_max_timeout()));
- done_event.Reset();
- EXPECT_TRUE(done_event.TimedWait(TestTimeouts::action_max_timeout()));
- done_event.Reset();
- EXPECT_TRUE(done_event.TimedWait(TestTimeouts::action_max_timeout()));
+ // changed to two different sizes while capturing. The mock for
+ // OnIncomingCapturedData() will use FormatChecker to examine the format of
+ // each frame being delivered.
+ for (int i = 0; i < 3; ++i) {
+ EXPECT_TRUE(done_event.TimedWait(TestTimeouts::action_max_timeout()));
+ done_event.Reset();
+ }
capture_device_->StopAndDeAllocate();
-
- EXPECT_EQ(kTestFrameWidth1, format.frame_size.width());
- EXPECT_EQ(kTestFrameHeight1, format.frame_size.height());
- EXPECT_EQ(kFrameRate, format.frame_rate);
- EXPECT_EQ(media::PIXEL_FORMAT_ARGB, format.pixel_format);
}
// This test verifies that an unpacked frame is converted to a packed frame.
diff --git a/content/browser/media/capture/web_contents_audio_input_stream.cc b/content/browser/media/capture/web_contents_audio_input_stream.cc
index 629d106..8fd1f97 100644
--- a/content/browser/media/capture/web_contents_audio_input_stream.cc
+++ b/content/browser/media/capture/web_contents_audio_input_stream.cc
@@ -91,7 +91,7 @@ class WebContentsAudioInputStream::Impl
// Called by WebContentsTracker when the target of the audio mirroring has
// changed.
- void OnTargetChanged(RenderWidgetHost* target);
+ void OnTargetChanged(bool had_target);
// Injected dependencies.
const int initial_render_process_id_;
@@ -305,11 +305,10 @@ void WebContentsAudioInputStream::Impl::ReleaseInput(
delete stream;
}
-void WebContentsAudioInputStream::Impl::OnTargetChanged(
- RenderWidgetHost* target) {
+void WebContentsAudioInputStream::Impl::OnTargetChanged(bool had_target) {
DCHECK(thread_checker_.CalledOnValidThread());
- is_target_lost_ = !target;
+ is_target_lost_ = !had_target;
if (state_ == MIRRORING) {
if (is_target_lost_) {
diff --git a/content/browser/media/capture/web_contents_audio_input_stream_unittest.cc b/content/browser/media/capture/web_contents_audio_input_stream_unittest.cc
index ffe8b4b..0eebd7a 100644
--- a/content/browser/media/capture/web_contents_audio_input_stream_unittest.cc
+++ b/content/browser/media/capture/web_contents_audio_input_stream_unittest.cc
@@ -356,13 +356,7 @@ class WebContentsAudioInputStreamTest : public testing::Test {
private:
void SimulateChangeCallback(int render_process_id, int render_frame_id) {
ASSERT_FALSE(change_callback_.is_null());
- if (render_process_id == -1 || render_frame_id == -1) {
- change_callback_.Run(NULL);
- } else {
- // For our tests, any non-NULL value will suffice since it will not be
- // dereferenced.
- change_callback_.Run(reinterpret_cast<RenderWidgetHost*>(0xdeadbee5));
- }
+ change_callback_.Run(render_process_id != -1 && render_frame_id != -1);
}
scoped_ptr<TestBrowserThreadBundle> thread_bundle_;
diff --git a/content/browser/media/capture/web_contents_tracker.cc b/content/browser/media/capture/web_contents_tracker.cc
index a9e161f..5007a58 100644
--- a/content/browser/media/capture/web_contents_tracker.cc
+++ b/content/browser/media/capture/web_contents_tracker.cc
@@ -44,6 +44,7 @@ void WebContentsTracker::Stop() {
DCHECK(task_runner_->BelongsToCurrentThread());
callback_.Reset();
+ resize_callback_.Reset();
if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
WebContentsObserver::Observe(NULL);
@@ -78,6 +79,12 @@ RenderWidgetHost* WebContentsTracker::GetTargetRenderWidgetHost() const {
return rwh;
}
+void WebContentsTracker::SetResizeChangeCallback(
+ const base::Closure& callback) {
+ DCHECK(!task_runner_.get() || task_runner_->BelongsToCurrentThread());
+ resize_callback_ = callback;
+}
+
void WebContentsTracker::OnPossibleTargetChange(bool force_callback_run) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
@@ -89,19 +96,29 @@ void WebContentsTracker::OnPossibleTargetChange(bool force_callback_run) {
last_target_ = rwh;
if (task_runner_->BelongsToCurrentThread()) {
- MaybeDoCallback(rwh);
- } else {
- task_runner_->PostTask(
- FROM_HERE,
- base::Bind(&WebContentsTracker::MaybeDoCallback, this, rwh));
+ MaybeDoCallback(rwh != nullptr);
+ return;
}
+
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&WebContentsTracker::MaybeDoCallback, this, rwh != nullptr));
}
-void WebContentsTracker::MaybeDoCallback(RenderWidgetHost* rwh) {
+void WebContentsTracker::MaybeDoCallback(bool was_still_tracking) {
DCHECK(task_runner_->BelongsToCurrentThread());
if (!callback_.is_null())
- callback_.Run(rwh);
+ callback_.Run(was_still_tracking);
+ if (was_still_tracking)
+ MaybeDoResizeCallback();
+}
+
+void WebContentsTracker::MaybeDoResizeCallback() {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+
+ if (!resize_callback_.is_null())
+ resize_callback_.Run();
}
void WebContentsTracker::StartObservingWebContents(int render_process_id,
@@ -128,6 +145,19 @@ void WebContentsTracker::RenderFrameHostChanged(RenderFrameHost* old_host,
OnPossibleTargetChange(false);
}
+void WebContentsTracker::MainFrameWasResized(bool width_changed) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ if (task_runner_->BelongsToCurrentThread()) {
+ MaybeDoResizeCallback();
+ return;
+ }
+
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&WebContentsTracker::MaybeDoResizeCallback, this));
+}
+
void WebContentsTracker::WebContentsDestroyed() {
Observe(NULL);
OnPossibleTargetChange(false);
diff --git a/content/browser/media/capture/web_contents_tracker.h b/content/browser/media/capture/web_contents_tracker.h
index 613c971..591c5e3 100644
--- a/content/browser/media/capture/web_contents_tracker.h
+++ b/content/browser/media/capture/web_contents_tracker.h
@@ -40,15 +40,15 @@ class CONTENT_EXPORT WebContentsTracker
explicit WebContentsTracker(bool track_fullscreen_rwh);
// Callback to indicate a new RenderWidgetHost should be targeted for capture.
- // This is also invoked with NULL to indicate tracking will not continue
+ // This is also invoked with false to indicate tracking will not continue
// (i.e., the WebContents instance was not found or has been destroyed).
- typedef base::Callback<void(RenderWidgetHost* rwh)> ChangeCallback;
+ typedef base::Callback<void(bool was_still_tracking)> ChangeCallback;
// Start tracking. The last-known |render_process_id| and
// |main_render_frame_id| are provided, and |callback| will be run once to
- // indicate the current capture target (this may occur during the invocation
- // of Start(), or in the future). The callback will be invoked on the same
- // thread calling Start().
+ // indicate whether tracking successfully started (this may occur during the
+ // invocation of Start(), or in the future). The callback will be invoked on
+ // the same thread calling Start().
virtual void Start(int render_process_id, int main_render_frame_id,
const ChangeCallback& callback);
@@ -59,6 +59,12 @@ class CONTENT_EXPORT WebContentsTracker
// Current target. This must only be called on the UI BrowserThread.
RenderWidgetHost* GetTargetRenderWidgetHost() const;
+ // Set a callback that is run whenever the main frame of the WebContents is
+ // resized. This method must be called on the same thread that calls
+ // Start()/Stop(), and |callback| will be run on that same thread. Calling
+ // the Stop() method guarantees the callback will never be invoked again.
+ void SetResizeChangeCallback(const base::Closure& callback);
+
protected:
friend class base::RefCountedThreadSafe<WebContentsTracker>;
~WebContentsTracker() override;
@@ -72,7 +78,12 @@ class CONTENT_EXPORT WebContentsTracker
// Called on the thread that Start()/Stop() are called on. Checks whether the
// callback is still valid and, if so, runs it.
- void MaybeDoCallback(RenderWidgetHost* rwh);
+ void MaybeDoCallback(bool was_still_tracking);
+
+ // Called on the thread that Start()/Stop() are called on. Checks whether the
+ // callback is still valid and, if so, runs it to indicate the main frame has
+ // changed in size.
+ void MaybeDoResizeCallback();
// Look-up the current WebContents instance associated with the given
// |render_process_id| and |main_render_frame_id| and begin observing it.
@@ -86,6 +97,10 @@ class CONTENT_EXPORT WebContentsTracker
void RenderFrameHostChanged(RenderFrameHost* old_host,
RenderFrameHost* new_host) override;
+ // WebContentsObserver override to notify the client that the source size has
+ // changed.
+ void MainFrameWasResized(bool width_changed) override;
+
// WebContentsObserver override to notify the client that the capture target
// has been permanently lost.
void WebContentsDestroyed() override;
@@ -109,6 +124,9 @@ class CONTENT_EXPORT WebContentsTracker
// This is used to eliminate duplicate callback runs.
RenderWidgetHost* last_target_;
+ // Callback to run when the target RenderWidgetHost has resized.
+ base::Closure resize_callback_;
+
DISALLOW_COPY_AND_ASSIGN(WebContentsTracker);
};
diff --git a/content/browser/media/capture/web_contents_video_capture_device.cc b/content/browser/media/capture/web_contents_video_capture_device.cc
index 8380682..292d856 100644
--- a/content/browser/media/capture/web_contents_video_capture_device.cc
+++ b/content/browser/media/capture/web_contents_video_capture_device.cc
@@ -79,7 +79,6 @@
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/display.h"
-#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/gfx/screen.h"
@@ -87,20 +86,6 @@ namespace content {
namespace {
-// Compute a letterbox region, aligned to even coordinates.
-gfx::Rect ComputeYV12LetterboxRegion(const gfx::Rect& visible_rect,
- const gfx::Size& content_size) {
-
- gfx::Rect result = media::ComputeLetterboxRegion(visible_rect, content_size);
-
- result.set_x(MakeEven(result.x()));
- result.set_y(MakeEven(result.y()));
- result.set_width(std::max(kMinFrameWidth, MakeEven(result.width())));
- result.set_height(std::max(kMinFrameHeight, MakeEven(result.height())));
-
- return result;
-}
-
void DeleteOnWorkerThread(scoped_ptr<base::Thread> render_thread,
const base::Closure& callback) {
render_thread.reset();
@@ -254,8 +239,12 @@ class WebContentsCaptureMachine : public VideoCaptureMachine {
deliver_frame_cb,
bool success);
- // Remove the old subscription, and start a new one if |rwh| is not NULL.
- void RenewFrameSubscription(RenderWidgetHost* rwh);
+ // Remove the old subscription, and attempt to start a new one if |had_target|
+ // is true.
+ void RenewFrameSubscription(bool had_target);
+
+ // Called whenever the render widget is resized.
+ void UpdateCaptureSize();
// Parameters saved in constructor.
const int initial_render_process_id_;
@@ -396,7 +385,7 @@ void RenderVideoFrame(const SkBitmap& input,
// Calculate the width and height of the content region in the |output|, based
// on the aspect ratio of |input|.
- gfx::Rect region_in_frame = ComputeYV12LetterboxRegion(
+ const gfx::Rect region_in_frame = media::ComputeLetterboxRegion(
output->visible_rect(), gfx::Size(input.width(), input.height()));
// Scale the bitmap to the required size, if necessary.
@@ -425,12 +414,20 @@ void RenderVideoFrame(const SkBitmap& input,
TRACE_EVENT_ASYNC_STEP_INTO0("gpu.capture", "Capture", output.get(), "YUV");
{
- SkAutoLockPixels scaled_bitmap_locker(scaled_bitmap);
+ // Align to 2x2 pixel boundaries, as required by
+ // media::CopyRGBToVideoFrame().
+ const gfx::Rect region_in_yv12_frame(region_in_frame.x() & ~1,
+ region_in_frame.y() & ~1,
+ region_in_frame.width() & ~1,
+ region_in_frame.height() & ~1);
+ if (region_in_yv12_frame.IsEmpty())
+ return;
+ SkAutoLockPixels scaled_bitmap_locker(scaled_bitmap);
media::CopyRGBToVideoFrame(
reinterpret_cast<uint8*>(scaled_bitmap.getPixels()),
scaled_bitmap.rowBytes(),
- region_in_frame,
+ region_in_yv12_frame,
output.get());
}
@@ -501,6 +498,9 @@ bool WebContentsCaptureMachine::Start(
// Note: Creation of the first WeakPtr in the following statement will cause
// IsStarted() to return true from now on.
+ tracker_->SetResizeChangeCallback(
+ base::Bind(&WebContentsCaptureMachine::UpdateCaptureSize,
+ weak_ptr_factory_.GetWeakPtr()));
tracker_->Start(initial_render_process_id_, initial_main_render_frame_id_,
base::Bind(&WebContentsCaptureMachine::RenewFrameSubscription,
weak_ptr_factory_.GetWeakPtr()));
@@ -522,7 +522,7 @@ void WebContentsCaptureMachine::Stop(const base::Closure& callback) {
// Note: RenewFrameSubscription() must be called before stopping |tracker_| so
// the web_contents() can be notified that the capturing is ending.
- RenewFrameSubscription(NULL);
+ RenewFrameSubscription(false);
tracker_->Stop();
// The render thread cannot be stopped on the UI thread, so post a message
@@ -551,11 +551,6 @@ void WebContentsCaptureMachine::Capture(
}
gfx::Size view_size = view->GetViewBounds().size();
- gfx::Size fitted_size;
- if (!view_size.IsEmpty()) {
- fitted_size = ComputeYV12LetterboxRegion(target->visible_rect(),
- view_size).size();
- }
if (view_size != last_view_size_) {
last_view_size_ = view_size;
@@ -574,6 +569,8 @@ void WebContentsCaptureMachine::Capture(
weak_ptr_factory_.GetWeakPtr(),
start_time, deliver_frame_cb));
} else {
+ const gfx::Size fitted_size = view_size.IsEmpty() ? gfx::Size() :
+ media::ComputeLetterboxRegion(target->visible_rect(), view_size).size();
rwh->CopyFromBackingStore(
gfx::Rect(),
fitted_size, // Size here is a request not always honored.
@@ -589,7 +586,10 @@ void WebContentsCaptureMachine::Capture(
gfx::Size WebContentsCaptureMachine::ComputeOptimalTargetSize() const {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
- gfx::Size optimal_size = oracle_proxy_->GetCaptureSize();
+ // TODO(miu): Propagate capture frame size changes as new "preferred size"
+ // updates, rather than just using the max frame size.
+ // http://crbug.com/350491
+ gfx::Size optimal_size = oracle_proxy_->max_frame_size();
// If the ratio between physical and logical pixels is greater than 1:1,
// shrink |optimal_size| by that amount. Then, when external code resizes the
@@ -657,9 +657,12 @@ void WebContentsCaptureMachine::DidCopyFromCompositingSurfaceToVideoFrame(
deliver_frame_cb.Run(start_time, success);
}
-void WebContentsCaptureMachine::RenewFrameSubscription(RenderWidgetHost* rwh) {
+void WebContentsCaptureMachine::RenewFrameSubscription(bool had_target) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ RenderWidgetHost* const rwh =
+ had_target ? tracker_->GetTargetRenderWidgetHost() : nullptr;
+
// Always destroy the old subscription before creating a new one.
const bool had_subscription = !!subscription_;
subscription_.reset();
@@ -688,6 +691,18 @@ void WebContentsCaptureMachine::RenewFrameSubscription(RenderWidgetHost* rwh) {
weak_ptr_factory_.GetWeakPtr())));
}
+void WebContentsCaptureMachine::UpdateCaptureSize() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ if (!oracle_proxy_)
+ return;
+ RenderWidgetHost* const rwh = tracker_->GetTargetRenderWidgetHost();
+ RenderWidgetHostView* const view = rwh ? rwh->GetView() : nullptr;
+ if (!view)
+ return;
+ oracle_proxy_->UpdateCaptureSize(view->GetViewBounds().size());
+}
+
} // namespace
WebContentsVideoCaptureDevice::WebContentsVideoCaptureDevice(
diff --git a/content/browser/media/capture/web_contents_video_capture_device_unittest.cc b/content/browser/media/capture/web_contents_video_capture_device_unittest.cc
index ab430a7..02b297f 100644
--- a/content/browser/media/capture/web_contents_video_capture_device_unittest.cc
+++ b/content/browser/media/capture/web_contents_video_capture_device_unittest.cc
@@ -11,11 +11,13 @@
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "content/browser/browser_thread_impl.h"
+#include "content/browser/frame_host/render_frame_host_impl.h"
#include "content/browser/media/capture/video_capture_oracle.h"
#include "content/browser/media/capture/web_contents_capture_util.h"
#include "content/browser/renderer_host/media/video_capture_buffer_pool.h"
#include "content/browser/renderer_host/render_view_host_factory.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_widget_host_view_frame_subscriber.h"
@@ -77,7 +79,6 @@ SkColor ConvertRgbToYuv(SkColor rgb) {
// counted.
class CaptureTestSourceController {
public:
-
CaptureTestSourceController()
: color_(SK_ColorMAGENTA),
copy_result_size_(kTestWidth, kTestHeight),
@@ -162,13 +163,22 @@ class CaptureTestView : public TestRenderWidgetHostView {
explicit CaptureTestView(RenderWidgetHostImpl* rwh,
CaptureTestSourceController* controller)
: TestRenderWidgetHostView(rwh),
- controller_(controller) {}
+ controller_(controller),
+ fake_bounds_(100, 100, 100 + kTestWidth, 100 + kTestHeight) {}
~CaptureTestView() override {}
// TestRenderWidgetHostView overrides.
gfx::Rect GetViewBounds() const override {
- return gfx::Rect(100, 100, 100 + kTestWidth, 100 + kTestHeight);
+ return fake_bounds_;
+ }
+
+ void SetSize(const gfx::Size& size) override {
+ SetBounds(gfx::Rect(fake_bounds_.origin(), size));
+ }
+
+ void SetBounds(const gfx::Rect& rect) override {
+ fake_bounds_ = rect;
}
bool CanCopyToVideoFrame() const override {
@@ -213,6 +223,7 @@ class CaptureTestView : public TestRenderWidgetHostView {
private:
scoped_ptr<RenderWidgetHostViewFrameSubscriber> subscriber_;
CaptureTestSourceController* const controller_;
+ gfx::Rect fake_bounds_;
DISALLOW_IMPLICIT_CONSTRUCTORS(CaptureTestView);
};
@@ -308,9 +319,10 @@ class CaptureTestRenderViewHostFactory : public RenderViewHostFactory {
// WebContentsVideoCaptureDevice.
class StubClient : public media::VideoCaptureDevice::Client {
public:
- StubClient(const base::Callback<void(SkColor)>& color_callback,
- const base::Closure& error_callback)
- : color_callback_(color_callback),
+ StubClient(
+ const base::Callback<void(SkColor, const gfx::Size&)>& report_callback,
+ const base::Closure& error_callback)
+ : report_callback_(report_callback),
error_callback_(error_callback) {
buffer_pool_ = new VideoCaptureBufferPool(2);
}
@@ -360,20 +372,29 @@ class StubClient : public media::VideoCaptureDevice::Client {
scoped_ptr<Buffer> buffer,
const scoped_refptr<media::VideoFrame>& frame,
const base::TimeTicks& timestamp) override {
- EXPECT_EQ(gfx::Size(kTestWidth, kTestHeight), frame->visible_rect().size());
+ EXPECT_FALSE(frame->visible_rect().IsEmpty());
EXPECT_EQ(media::VideoFrame::I420, frame->format());
double frame_rate = 0;
EXPECT_TRUE(
frame->metadata()->GetDouble(media::VideoFrameMetadata::FRAME_RATE,
&frame_rate));
EXPECT_EQ(kTestFramesPerSecond, frame_rate);
- uint8 yuv[3];
- for (int plane = 0; plane < 3; ++plane)
- yuv[plane] = frame->visible_data(plane)[0];
- // TODO(nick): We just look at the first pixel presently, because if
- // the analysis is too slow, the backlog of frames will grow without bound
- // and trouble erupts. http://crbug.com/174519
- color_callback_.Run((SkColorSetRGB(yuv[0], yuv[1], yuv[2])));
+
+ // TODO(miu): We just look at the center pixel presently, because if the
+ // analysis is too slow, the backlog of frames will grow without bound and
+ // trouble erupts. http://crbug.com/174519
+ using media::VideoFrame;
+ const gfx::Point center = frame->visible_rect().CenterPoint();
+ const int center_offset_y =
+ (frame->stride(VideoFrame::kYPlane) * center.y()) + center.x();
+ const int center_offset_uv =
+ (frame->stride(VideoFrame::kUPlane) * (center.y() / 2)) +
+ (center.x() / 2);
+ report_callback_.Run(
+ SkColorSetRGB(frame->data(VideoFrame::kYPlane)[center_offset_y],
+ frame->data(VideoFrame::kUPlane)[center_offset_uv],
+ frame->data(VideoFrame::kVPlane)[center_offset_uv]),
+ frame->visible_rect().size());
}
void OnError(const std::string& reason) override { error_callback_.Run(); }
@@ -407,7 +428,7 @@ class StubClient : public media::VideoCaptureDevice::Client {
};
scoped_refptr<VideoCaptureBufferPool> buffer_pool_;
- base::Callback<void(SkColor)> color_callback_;
+ base::Callback<void(SkColor, const gfx::Size&)> report_callback_;
base::Closure error_callback_;
DISALLOW_COPY_AND_ASSIGN(StubClient);
@@ -417,9 +438,11 @@ class StubClientObserver {
public:
StubClientObserver()
: error_encountered_(false),
- wait_color_yuv_(0xcafe1950) {
+ wait_color_yuv_(0xcafe1950),
+ wait_size_(kTestWidth, kTestHeight) {
client_.reset(new StubClient(
- base::Bind(&StubClientObserver::OnColor, base::Unretained(this)),
+ base::Bind(&StubClientObserver::DidDeliverFrame,
+ base::Unretained(this)),
base::Bind(&StubClientObserver::OnError, base::Unretained(this))));
}
@@ -429,16 +452,30 @@ class StubClientObserver {
return client_.Pass();
}
- void QuitIfConditionMet(SkColor color) {
+ void QuitIfConditionsMet(SkColor color, const gfx::Size& size) {
base::AutoLock guard(lock_);
- if (wait_color_yuv_ == color || error_encountered_)
+ if (error_encountered_)
+ base::MessageLoop::current()->Quit();
+ else if (wait_color_yuv_ == color && wait_size_.IsEmpty())
+ base::MessageLoop::current()->Quit();
+ else if (wait_color_yuv_ == color && wait_size_ == size)
base::MessageLoop::current()->Quit();
}
+ // Run the current loop until a frame is delivered with the |expected_color|
+ // and any non-empty frame size.
void WaitForNextColor(SkColor expected_color) {
+ WaitForNextColorAndFrameSize(expected_color, gfx::Size());
+ }
+
+ // Run the current loop until a frame is delivered with the |expected_color|
+ // and is of the |expected_size|.
+ void WaitForNextColorAndFrameSize(SkColor expected_color,
+ const gfx::Size& expected_size) {
{
base::AutoLock guard(lock_);
wait_color_yuv_ = ConvertRgbToYuv(expected_color);
+ wait_size_ = expected_size;
error_encountered_ = false;
}
RunCurrentLoopWithDeadline();
@@ -452,6 +489,7 @@ class StubClientObserver {
{
base::AutoLock guard(lock_);
wait_color_yuv_ = kNotInterested;
+ wait_size_ = gfx::Size();
error_encountered_ = false;
}
RunCurrentLoopWithDeadline();
@@ -472,22 +510,25 @@ class StubClientObserver {
error_encountered_ = true;
}
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
- &StubClientObserver::QuitIfConditionMet,
+ &StubClientObserver::QuitIfConditionsMet,
base::Unretained(this),
- kNothingYet));
+ kNothingYet,
+ gfx::Size()));
}
- void OnColor(SkColor color) {
+ void DidDeliverFrame(SkColor color, const gfx::Size& size) {
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
- &StubClientObserver::QuitIfConditionMet,
+ &StubClientObserver::QuitIfConditionsMet,
base::Unretained(this),
- color));
+ color,
+ size));
}
private:
base::Lock lock_;
bool error_encountered_;
SkColor wait_color_yuv_;
+ gfx::Size wait_size_;
scoped_ptr<StubClient> client_;
DISALLOW_COPY_AND_ASSIGN(StubClientObserver);
@@ -622,6 +663,22 @@ class WebContentsVideoCaptureDeviceTest : public testing::Test {
}
}
+ void SimulateSourceSizeChange(const gfx::Size& size) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ CaptureTestView* test_view = static_cast<CaptureTestView*>(
+ web_contents_->GetRenderViewHost()->GetView());
+ test_view->SetSize(size);
+ // Normally, RenderWidgetHostImpl would notify WebContentsImpl that the size
+ // has changed. However, in this test setup where there is no render
+ // process, we must notify WebContentsImpl directly.
+ WebContentsImpl* const as_web_contents_impl =
+ static_cast<WebContentsImpl*>(web_contents_.get());
+ RenderWidgetHostDelegate* const as_rwh_delegate =
+ static_cast<RenderWidgetHostDelegate*>(as_web_contents_impl);
+ as_rwh_delegate->RenderWidgetWasResized(
+ as_web_contents_impl->GetMainFrame()->GetRenderWidgetHost(), true);
+ }
+
void DestroyVideoCaptureDevice() { device_.reset(); }
StubClientObserver* client_observer() {
@@ -879,5 +936,158 @@ TEST_F(WebContentsVideoCaptureDeviceTest, BadFramesGoodFrames) {
device()->StopAndDeAllocate();
}
+// Tests that, when configured with the FIXED_ASPECT_RATIO resolution change
+// policy, the source size changes result in video frames of possibly varying
+// resolutions, but all with the same aspect ratio.
+TEST_F(WebContentsVideoCaptureDeviceTest, VariableResolution_FixedAspectRatio) {
+ media::VideoCaptureParams capture_params;
+ capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight);
+ capture_params.requested_format.frame_rate = kTestFramesPerSecond;
+ capture_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420;
+ capture_params.resolution_change_policy =
+ media::RESOLUTION_POLICY_FIXED_ASPECT_RATIO;
+
+ device()->AllocateAndStart(capture_params, client_observer()->PassClient());
+
+ for (int i = 0; i < 6; i++) {
+ const char* name = NULL;
+ switch (i % 3) {
+ case 0:
+ source()->SetCanCopyToVideoFrame(true);
+ source()->SetUseFrameSubscriber(false);
+ name = "VideoFrame";
+ break;
+ case 1:
+ source()->SetCanCopyToVideoFrame(false);
+ source()->SetUseFrameSubscriber(true);
+ name = "Subscriber";
+ break;
+ case 2:
+ source()->SetCanCopyToVideoFrame(false);
+ source()->SetUseFrameSubscriber(false);
+ name = "SkBitmap";
+ break;
+ default:
+ FAIL();
+ }
+
+ SCOPED_TRACE(base::StringPrintf("Using %s path, iteration #%d", name, i));
+
+ // Source size equals maximum size. Expect delivered frames to be
+ // kTestWidth by kTestHeight.
+ source()->SetSolidColor(SK_ColorRED);
+ SimulateSourceSizeChange(gfx::Size(kTestWidth, kTestHeight));
+ SimulateDrawEvent();
+ ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize(
+ SK_ColorRED, gfx::Size(kTestWidth, kTestHeight)));
+
+ // Source size is half in both dimensions. Expect delivered frames to be of
+ // the same aspect ratio as kTestWidth by kTestHeight, but larger than the
+ // half size because the minimum height is 180 lines.
+ source()->SetSolidColor(SK_ColorGREEN);
+ SimulateSourceSizeChange(gfx::Size(kTestWidth / 2, kTestHeight / 2));
+ SimulateDrawEvent();
+ ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize(
+ SK_ColorGREEN, gfx::Size(180 * kTestWidth / kTestHeight, 180)));
+
+ // Source size changes aspect ratio. Expect delivered frames to be padded
+ // in the horizontal dimension to preserve aspect ratio.
+ source()->SetSolidColor(SK_ColorBLUE);
+ SimulateSourceSizeChange(gfx::Size(kTestWidth / 2, kTestHeight));
+ SimulateDrawEvent();
+ ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize(
+ SK_ColorBLUE, gfx::Size(kTestWidth, kTestHeight)));
+
+ // Source size changes aspect ratio again. Expect delivered frames to be
+ // padded in the vertical dimension to preserve aspect ratio.
+ source()->SetSolidColor(SK_ColorBLACK);
+ SimulateSourceSizeChange(gfx::Size(kTestWidth, kTestHeight / 2));
+ SimulateDrawEvent();
+ ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize(
+ SK_ColorBLACK, gfx::Size(kTestWidth, kTestHeight)));
+ }
+
+ device()->StopAndDeAllocate();
+}
+
+// Tests that, when configured with the ANY_WITHIN_LIMIT resolution change
+// policy, the source size changes result in video frames of possibly varying
+// resolutions.
+TEST_F(WebContentsVideoCaptureDeviceTest, VariableResolution_AnyWithinLimits) {
+ media::VideoCaptureParams capture_params;
+ capture_params.requested_format.frame_size.SetSize(kTestWidth, kTestHeight);
+ capture_params.requested_format.frame_rate = kTestFramesPerSecond;
+ capture_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420;
+ capture_params.resolution_change_policy =
+ media::RESOLUTION_POLICY_ANY_WITHIN_LIMIT;
+
+ device()->AllocateAndStart(capture_params, client_observer()->PassClient());
+
+ for (int i = 0; i < 6; i++) {
+ const char* name = NULL;
+ switch (i % 3) {
+ case 0:
+ source()->SetCanCopyToVideoFrame(true);
+ source()->SetUseFrameSubscriber(false);
+ name = "VideoFrame";
+ break;
+ case 1:
+ source()->SetCanCopyToVideoFrame(false);
+ source()->SetUseFrameSubscriber(true);
+ name = "Subscriber";
+ break;
+ case 2:
+ source()->SetCanCopyToVideoFrame(false);
+ source()->SetUseFrameSubscriber(false);
+ name = "SkBitmap";
+ break;
+ default:
+ FAIL();
+ }
+
+ SCOPED_TRACE(base::StringPrintf("Using %s path, iteration #%d", name, i));
+
+ // Source size equals maximum size. Expect delivered frames to be
+ // kTestWidth by kTestHeight.
+ source()->SetSolidColor(SK_ColorRED);
+ SimulateSourceSizeChange(gfx::Size(kTestWidth, kTestHeight));
+ SimulateDrawEvent();
+ ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize(
+ SK_ColorRED, gfx::Size(kTestWidth, kTestHeight)));
+
+ // Source size is half in both dimensions. Expect delivered frames to also
+ // be half in both dimensions.
+ source()->SetSolidColor(SK_ColorGREEN);
+ SimulateSourceSizeChange(gfx::Size(kTestWidth / 2, kTestHeight / 2));
+ SimulateDrawEvent();
+ ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize(
+ SK_ColorGREEN, gfx::Size(kTestWidth / 2, kTestHeight / 2)));
+
+ // Source size changes to something arbitrary. Since the source size is
+ // less than the maximum size, expect delivered frames to be the same size
+ // as the source size.
+ source()->SetSolidColor(SK_ColorBLUE);
+ gfx::Size arbitrary_source_size(kTestWidth / 2 + 42, kTestHeight - 10);
+ SimulateSourceSizeChange(arbitrary_source_size);
+ SimulateDrawEvent();
+ ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize(
+ SK_ColorBLUE, arbitrary_source_size));
+
+ // Source size changes to something arbitrary that exceeds the maximum frame
+ // size. Since the source size exceeds the maximum size, expect delivered
+ // frames to be downscaled.
+ source()->SetSolidColor(SK_ColorBLACK);
+ arbitrary_source_size = gfx::Size(kTestWidth * 2 + 99, kTestHeight / 2);
+ SimulateSourceSizeChange(arbitrary_source_size);
+ SimulateDrawEvent();
+ ASSERT_NO_FATAL_FAILURE(client_observer()->WaitForNextColorAndFrameSize(
+ SK_ColorBLACK, gfx::Size(kTestWidth,
+ kTestWidth * arbitrary_source_size.height() /
+ arbitrary_source_size.width())));
+ }
+
+ device()->StopAndDeAllocate();
+}
+
} // namespace
} // namespace content
diff --git a/content/content_browser.gypi b/content/content_browser.gypi
index 6f563ce..9ae48ee8 100644
--- a/content/content_browser.gypi
+++ b/content/content_browser.gypi
@@ -973,6 +973,8 @@
'browser/media/capture/animated_content_sampler.h',
'browser/media/capture/audio_mirroring_manager.cc',
'browser/media/capture/audio_mirroring_manager.h',
+ 'browser/media/capture/capture_resolution_chooser.cc',
+ 'browser/media/capture/capture_resolution_chooser.h',
'browser/media/capture/content_video_capture_device_core.cc',
'browser/media/capture/content_video_capture_device_core.h',
'browser/media/capture/feedback_signal_accumulator.cc',
diff --git a/content/content_tests.gypi b/content/content_tests.gypi
index 188141a..86ac1d2 100644
--- a/content/content_tests.gypi
+++ b/content/content_tests.gypi
@@ -471,6 +471,7 @@
'browser/media/audio_stream_monitor_unittest.cc',
'browser/media/capture/animated_content_sampler_unittest.cc',
'browser/media/capture/audio_mirroring_manager_unittest.cc',
+ 'browser/media/capture/capture_resolution_chooser_unittest.cc',
'browser/media/capture/feedback_signal_accumulator_unittest.cc',
'browser/media/capture/smooth_event_sampler_unittest.cc',
'browser/media/capture/video_capture_oracle_unittest.cc',
diff --git a/media/base/video_util.cc b/media/base/video_util.cc
index d794674..e04a5a3 100644
--- a/media/base/video_util.cc
+++ b/media/base/video_util.cc
@@ -270,6 +270,23 @@ void RotatePlaneByPixels(
}
}
+// Common logic for the letterboxing and scale-within/scale-encompassing
+// functions. Scales |size| to either fit within or encompass |target|,
+// depending on whether |fit_within_target| is true.
+static gfx::Size ScaleSizeToTarget(const gfx::Size& size,
+ const gfx::Size& target,
+ bool fit_within_target) {
+ if (size.IsEmpty())
+ return gfx::Size(); // Corner case: Aspect ratio is undefined.
+
+ const int64 x = static_cast<int64>(size.width()) * target.height();
+ const int64 y = static_cast<int64>(size.height()) * target.width();
+ const bool use_target_width = fit_within_target ? (y < x) : (x < y);
+ return use_target_width ?
+ gfx::Size(target.width(), static_cast<int>(y / size.width())) :
+ gfx::Size(static_cast<int>(x / size.height()), target.height());
+}
+
gfx::Rect ComputeLetterboxRegion(const gfx::Rect& bounds,
const gfx::Size& content) {
// If |content| has an undefined aspect ratio, let's not try to divide by
@@ -277,19 +294,33 @@ gfx::Rect ComputeLetterboxRegion(const gfx::Rect& bounds,
if (content.IsEmpty())
return gfx::Rect();
- int64 x = static_cast<int64>(content.width()) * bounds.height();
- int64 y = static_cast<int64>(content.height()) * bounds.width();
-
- gfx::Size letterbox(bounds.width(), bounds.height());
- if (y < x)
- letterbox.set_height(static_cast<int>(y / content.width()));
- else
- letterbox.set_width(static_cast<int>(x / content.height()));
gfx::Rect result = bounds;
- result.ClampToCenteredSize(letterbox);
+ result.ClampToCenteredSize(ScaleSizeToTarget(content, bounds.size(), true));
return result;
}
+gfx::Size ScaleSizeToFitWithinTarget(const gfx::Size& size,
+ const gfx::Size& target) {
+ return ScaleSizeToTarget(size, target, true);
+}
+
+gfx::Size ScaleSizeToEncompassTarget(const gfx::Size& size,
+ const gfx::Size& target) {
+ return ScaleSizeToTarget(size, target, false);
+}
+
+gfx::Size PadToMatchAspectRatio(const gfx::Size& size,
+ const gfx::Size& target) {
+ if (target.IsEmpty())
+ return gfx::Size(); // Aspect ratio is undefined.
+
+ const int64 x = static_cast<int64>(size.width()) * target.height();
+ const int64 y = static_cast<int64>(size.height()) * target.width();
+ if (x < y)
+ return gfx::Size(static_cast<int>(y / target.height()), size.height());
+ return gfx::Size(size.width(), static_cast<int>(x / target.width()));
+}
+
void CopyRGBToVideoFrame(const uint8* source,
int stride,
const gfx::Rect& region_in_frame,
diff --git a/media/base/video_util.h b/media/base/video_util.h
index 7798ebe..abbcad4 100644
--- a/media/base/video_util.h
+++ b/media/base/video_util.h
@@ -84,6 +84,29 @@ MEDIA_EXPORT void RotatePlaneByPixels(
MEDIA_EXPORT gfx::Rect ComputeLetterboxRegion(const gfx::Rect& bounds,
const gfx::Size& content);
+// Return a scaled |size| whose area is less than or equal to |target|, where
+// one of its dimensions is equal to |target|'s. The aspect ratio of |size| is
+// preserved as closely as possible. If |size| is empty, the result will be
+// empty.
+MEDIA_EXPORT gfx::Size ScaleSizeToFitWithinTarget(const gfx::Size& size,
+ const gfx::Size& target);
+
+// Return a scaled |size| whose area is greater than or equal to |target|, where
+// one of its dimensions is equal to |target|'s. The aspect ratio of |size| is
+// preserved as closely as possible. If |size| is empty, the result will be
+// empty.
+MEDIA_EXPORT gfx::Size ScaleSizeToEncompassTarget(const gfx::Size& size,
+ const gfx::Size& target);
+
+// Returns |size| with only one of its dimensions increased such that the result
+// matches the aspect ratio of |target|. This is different from
+// ScaleSizeToEncompassTarget() in two ways: 1) The goal is to match the aspect
+// ratio of |target| rather than that of |size|. 2) Only one of the dimensions
+// of |size| may change, and it may only be increased (padded). If either
+// |size| or |target| is empty, the result will be empty.
+MEDIA_EXPORT gfx::Size PadToMatchAspectRatio(const gfx::Size& size,
+ const gfx::Size& target);
+
// Copy an RGB bitmap into the specified |region_in_frame| of a YUV video frame.
// Fills the regions outside |region_in_frame| with black.
MEDIA_EXPORT void CopyRGBToVideoFrame(const uint8* source,
diff --git a/media/base/video_util_unittest.cc b/media/base/video_util_unittest.cc
index 9ac13c1..79c5315 100644
--- a/media/base/video_util_unittest.cc
+++ b/media/base/video_util_unittest.cc
@@ -328,6 +328,8 @@ TEST_P(VideoUtilRotationTest, Rotate) {
INSTANTIATE_TEST_CASE_P(, VideoUtilRotationTest,
testing::ValuesIn(kVideoRotationTestData));
+// Tests the ComputeLetterboxRegion function. Also, because of shared code
+// internally, this also tests ScaleSizeToFitWithinTarget().
TEST_F(VideoUtilTest, ComputeLetterboxRegion) {
EXPECT_EQ(gfx::Rect(167, 0, 666, 500),
ComputeLetterboxRegion(gfx::Rect(0, 0, 1000, 500),
@@ -348,6 +350,48 @@ TEST_F(VideoUtilTest, ComputeLetterboxRegion) {
gfx::Size(0, 0)).IsEmpty());
}
+TEST_F(VideoUtilTest, ScaleSizeToEncompassTarget) {
+ EXPECT_EQ(gfx::Size(1000, 750),
+ ScaleSizeToEncompassTarget(gfx::Size(640, 480),
+ gfx::Size(1000, 500)));
+ EXPECT_EQ(gfx::Size(1333, 1000),
+ ScaleSizeToEncompassTarget(gfx::Size(640, 480),
+ gfx::Size(500, 1000)));
+ EXPECT_EQ(gfx::Size(1000, 562),
+ ScaleSizeToEncompassTarget(gfx::Size(1920, 1080),
+ gfx::Size(1000, 500)));
+ EXPECT_EQ(gfx::Size(133, 100),
+ ScaleSizeToEncompassTarget(gfx::Size(400, 300),
+ gfx::Size(100, 100)));
+ EXPECT_EQ(gfx::Size(2666666666, 2000000000),
+ ScaleSizeToEncompassTarget(gfx::Size(40000, 30000),
+ gfx::Size(2000000000, 2000000000)));
+ EXPECT_TRUE(ScaleSizeToEncompassTarget(
+ gfx::Size(0, 0), gfx::Size(2000000000, 2000000000)).IsEmpty());
+}
+
+TEST_F(VideoUtilTest, PadToMatchAspectRatio) {
+ EXPECT_EQ(gfx::Size(640, 480),
+ PadToMatchAspectRatio(gfx::Size(640, 480), gfx::Size(640, 480)));
+ EXPECT_EQ(gfx::Size(640, 480),
+ PadToMatchAspectRatio(gfx::Size(640, 480), gfx::Size(4, 3)));
+ EXPECT_EQ(gfx::Size(960, 480),
+ PadToMatchAspectRatio(gfx::Size(640, 480), gfx::Size(1000, 500)));
+ EXPECT_EQ(gfx::Size(640, 1280),
+ PadToMatchAspectRatio(gfx::Size(640, 480), gfx::Size(500, 1000)));
+ EXPECT_EQ(gfx::Size(2160, 1080),
+ PadToMatchAspectRatio(gfx::Size(1920, 1080), gfx::Size(1000, 500)));
+ EXPECT_EQ(gfx::Size(400, 400),
+ PadToMatchAspectRatio(gfx::Size(400, 300), gfx::Size(100, 100)));
+ EXPECT_EQ(gfx::Size(400, 400),
+ PadToMatchAspectRatio(gfx::Size(300, 400), gfx::Size(100, 100)));
+ EXPECT_EQ(gfx::Size(40000, 40000),
+ PadToMatchAspectRatio(gfx::Size(40000, 30000),
+ gfx::Size(2000000000, 2000000000)));
+ EXPECT_TRUE(PadToMatchAspectRatio(
+ gfx::Size(40000, 30000), gfx::Size(0, 0)).IsEmpty());
+}
+
TEST_F(VideoUtilTest, LetterboxYUV) {
int width = 40;
int height = 30;