Commit 39254e67 authored by miu's avatar miu Committed by Commit bot

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}
parent f54b8097
// 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
// 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_
// 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
...@@ -49,7 +49,9 @@ ThreadSafeCaptureOracle::ThreadSafeCaptureOracle( ...@@ -49,7 +49,9 @@ ThreadSafeCaptureOracle::ThreadSafeCaptureOracle(
oracle_(base::TimeDelta::FromMicroseconds( oracle_(base::TimeDelta::FromMicroseconds(
static_cast<int64>(1000000.0 / params.requested_format.frame_rate + static_cast<int64>(1000000.0 / params.requested_format.frame_rate +
0.5 /* to round to nearest int */))), 0.5 /* to round to nearest int */))),
params_(params) {} params_(params),
resolution_chooser_(params.requested_format.frame_size,
params.resolution_change_policy) {}
ThreadSafeCaptureOracle::~ThreadSafeCaptureOracle() {} ThreadSafeCaptureOracle::~ThreadSafeCaptureOracle() {}
...@@ -67,9 +69,7 @@ bool ThreadSafeCaptureOracle::ObserveEventAndDecideCapture( ...@@ -67,9 +69,7 @@ bool ThreadSafeCaptureOracle::ObserveEventAndDecideCapture(
if (!client_) if (!client_)
return false; // Capture is stopped. return false; // Capture is stopped.
if (capture_size_.IsEmpty()) const gfx::Size visible_size = resolution_chooser_.capture_size();
capture_size_ = max_frame_size();
const gfx::Size visible_size = capture_size_;
// Always round up the coded size to multiple of 16 pixels. // Always round up the coded size to multiple of 16 pixels.
// See http://crbug.com/402151. // See http://crbug.com/402151.
const gfx::Size coded_size((visible_size.width() + 15) & ~15, const gfx::Size coded_size((visible_size.width() + 15) & ~15,
...@@ -141,30 +141,15 @@ bool ThreadSafeCaptureOracle::ObserveEventAndDecideCapture( ...@@ -141,30 +141,15 @@ bool ThreadSafeCaptureOracle::ObserveEventAndDecideCapture(
gfx::Size ThreadSafeCaptureOracle::GetCaptureSize() const { gfx::Size ThreadSafeCaptureOracle::GetCaptureSize() const {
base::AutoLock guard(lock_); 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) { void ThreadSafeCaptureOracle::UpdateCaptureSize(const gfx::Size& source_size) {
base::AutoLock guard(lock_); base::AutoLock guard(lock_);
resolution_chooser_.SetSourceSize(source_size);
// Update |capture_size_| based on |source_size| if either: 1) The resolution VLOG(1) << "Source size changed to " << source_size.ToString()
// change policy specifies fixed frame sizes and |capture_size_| has not yet << " --> Capture size is now "
// been set; or 2) The resolution change policy specifies dynamic frame << resolution_chooser_.capture_size().ToString();
// 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())));
}
} }
void ThreadSafeCaptureOracle::Stop() { void ThreadSafeCaptureOracle::Stop() {
...@@ -234,23 +219,15 @@ void ContentVideoCaptureDeviceCore::AllocateAndStart( ...@@ -234,23 +219,15 @@ void ContentVideoCaptureDeviceCore::AllocateAndStart(
return; return;
} }
if (params.requested_format.frame_size.width() < kMinFrameWidth || if (params.requested_format.frame_size.IsEmpty()) {
params.requested_format.frame_size.height() < kMinFrameHeight) { std::string error_msg =
std::string error_msg = "invalid frame size: " + params.requested_format.frame_size.ToString();
"invalid frame size: " + params.requested_format.frame_size.ToString(); DVLOG(1) << error_msg;
DVLOG(1) << error_msg; client->OnError(error_msg);
client->OnError(error_msg); return;
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()));
oracle_proxy_ = new ThreadSafeCaptureOracle(client.Pass(), new_params); oracle_proxy_ = new ThreadSafeCaptureOracle(client.Pass(), params);
// Starts the capture machine asynchronously. // Starts the capture machine asynchronously.
BrowserThread::PostTaskAndReplyWithResult( BrowserThread::PostTaskAndReplyWithResult(
...@@ -259,7 +236,7 @@ void ContentVideoCaptureDeviceCore::AllocateAndStart( ...@@ -259,7 +236,7 @@ void ContentVideoCaptureDeviceCore::AllocateAndStart(
base::Bind(&VideoCaptureMachine::Start, base::Bind(&VideoCaptureMachine::Start,
base::Unretained(capture_machine_.get()), base::Unretained(capture_machine_.get()),
oracle_proxy_, oracle_proxy_,
new_params), params),
base::Bind(&ContentVideoCaptureDeviceCore::CaptureStarted, AsWeakPtr())); base::Bind(&ContentVideoCaptureDeviceCore::CaptureStarted, AsWeakPtr()));
TransitionStateTo(kCapturing); TransitionStateTo(kCapturing);
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "base/threading/thread.h" #include "base/threading/thread.h"
#include "base/threading/thread_checker.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/browser/media/capture/video_capture_oracle.h"
#include "content/common/content_export.h" #include "content/common/content_export.h"
#include "media/base/video_frame.h" #include "media/base/video_frame.h"
...@@ -23,15 +24,6 @@ class VideoFrame; ...@@ -23,15 +24,6 @@ class VideoFrame;
namespace content { 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; class VideoCaptureMachine;
// Thread-safe, refcounted proxy to the VideoCaptureOracle. This proxy wraps // Thread-safe, refcounted proxy to the VideoCaptureOracle. This proxy wraps
...@@ -104,8 +96,8 @@ class ThreadSafeCaptureOracle ...@@ -104,8 +96,8 @@ class ThreadSafeCaptureOracle
// The video capture parameters used to construct the oracle proxy. // The video capture parameters used to construct the oracle proxy.
const media::VideoCaptureParams params_; const media::VideoCaptureParams params_;
// The current video capture size. // Determines video capture frame sizes.
gfx::Size capture_size_; CaptureResolutionChooser resolution_chooser_;
}; };
// Keeps track of the video capture source frames and executes copying on the // Keeps track of the video capture source frames and executes copying on the
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include "base/synchronization/lock.h" #include "base/synchronization/lock.h"
#include "base/threading/thread.h" #include "base/threading/thread.h"
#include "base/timer/timer.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/browser/media/capture/desktop_capture_device_uma_types.h"
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
#include "content/public/browser/desktop_media_id.h" #include "content/public/browser/desktop_media_id.h"
...@@ -72,11 +73,6 @@ class DesktopCaptureDevice::Core : public webrtc::DesktopCapturer::Callback { ...@@ -72,11 +73,6 @@ class DesktopCaptureDevice::Core : public webrtc::DesktopCapturer::Callback {
webrtc::SharedMemory* CreateSharedMemory(size_t size) override; webrtc::SharedMemory* CreateSharedMemory(size_t size) override;
void OnCaptureCompleted(webrtc::DesktopFrame* frame) 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 // Method that is scheduled on |task_runner_| to be called on regular interval
// to capture a frame. // to capture a frame.
void OnCaptureTimer(); void OnCaptureTimer();
...@@ -97,24 +93,20 @@ class DesktopCaptureDevice::Core : public webrtc::DesktopCapturer::Callback { ...@@ -97,24 +93,20 @@ class DesktopCaptureDevice::Core : public webrtc::DesktopCapturer::Callback {
// on the task_runner_ thread. // on the task_runner_ thread.
scoped_ptr<Client> client_; scoped_ptr<Client> client_;
// Requested video capture format (width, height, frame rate, etc). // Requested video capture frame rate.
media::VideoCaptureParams requested_params_; float requested_frame_rate_;
// Actual video capture format being generated.
media::VideoCaptureFormat capture_format_;
// Size of frame most recently captured from the source. // Size of frame most recently captured from the source.
webrtc::DesktopSize previous_frame_size_; 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, // DesktopFrame into which captured frames are down-scaled and/or letterboxed,
// depending upon the caller's requested capture capabilities. If frames can // depending upon the caller's requested capture capabilities. If frames can
// be returned to the caller directly then this is NULL. // be returned to the caller directly then this is NULL.
scoped_ptr<webrtc::DesktopFrame> output_frame_; 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. // Timer used to capture the frame.
base::OneShotTimer<Core> capture_timer_; base::OneShotTimer<Core> capture_timer_;
...@@ -167,12 +159,10 @@ void DesktopCaptureDevice::Core::AllocateAndStart( ...@@ -167,12 +159,10 @@ void DesktopCaptureDevice::Core::AllocateAndStart(
DCHECK(!client_.get()); DCHECK(!client_.get());
client_ = client.Pass(); client_ = client.Pass();
requested_params_ = params; requested_frame_rate_ = params.requested_format.frame_rate;
resolution_chooser_.reset(new CaptureResolutionChooser(
capture_format_ = requested_params_.requested_format; params.requested_format.frame_size,
params.resolution_change_policy));
// This capturer always outputs ARGB, non-interlaced.
capture_format_.pixel_format = media::PIXEL_FORMAT_ARGB;
power_save_blocker_.reset( power_save_blocker_.reset(
PowerSaveBlocker::Create( PowerSaveBlocker::Create(
...@@ -238,16 +228,29 @@ void DesktopCaptureDevice::Core::OnCaptureCompleted( ...@@ -238,16 +228,29 @@ void DesktopCaptureDevice::Core::OnCaptureCompleted(
scoped_ptr<webrtc::DesktopFrame> owned_frame(frame); 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 // 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 // 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 // it with a black frame to avoid the video appearing frozen at the last
// frame. // frame.
if (frame->size().width() == 1 || frame->size().height() == 1) { if (frame->size().width() == 1 || frame->size().height() == 1) {
if (!black_frame_.get()) { if (!black_frame_.get()) {
black_frame_.reset( black_frame_.reset(new webrtc::BasicDesktopFrame(output_size));
new webrtc::BasicDesktopFrame(
webrtc::DesktopSize(capture_format_.frame_size.width(),
capture_format_.frame_size.height())));
memset(black_frame_->data(), memset(black_frame_->data(),
0, 0,
black_frame_->stride() * black_frame_->size().height()); black_frame_->stride() * black_frame_->size().height());
...@@ -256,11 +259,6 @@ void DesktopCaptureDevice::Core::OnCaptureCompleted( ...@@ -256,11 +259,6 @@ void DesktopCaptureDevice::Core::OnCaptureCompleted(
frame = black_frame_.get(); 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() * size_t output_bytes = output_size.width() * output_size.height() *
webrtc::DesktopFrame::kBytesPerPixel; webrtc::DesktopFrame::kBytesPerPixel;
const uint8_t* output_data = NULL; const uint8_t* output_data = NULL;
...@@ -270,7 +268,7 @@ void DesktopCaptureDevice::Core::OnCaptureCompleted( ...@@ -270,7 +268,7 @@ void DesktopCaptureDevice::Core::OnCaptureCompleted(
// match the output size. // match the output size.
// Allocate a buffer of the correct size to scale the frame into. // 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. // need to worry about clearing out stale pixel data in letterboxed areas.
if (!output_frame_) { if (!output_frame_) {
output_frame_.reset(new webrtc::BasicDesktopFrame(output_size)); output_frame_.reset(new webrtc::BasicDesktopFrame(output_size));
...@@ -280,13 +278,15 @@ void DesktopCaptureDevice::Core::OnCaptureCompleted( ...@@ -280,13 +278,15 @@ void DesktopCaptureDevice::Core::OnCaptureCompleted(
// TODO(wez): Optimize this to scale only changed portions of the output, // TODO(wez): Optimize this to scale only changed portions of the output,
// using ARGBScaleClip(). // using ARGBScaleClip().
const webrtc::DesktopRect output_rect =
ComputeLetterboxRect(output_size, frame->size());
uint8_t* output_rect_data = output_frame_->data() + uint8_t* output_rect_data = output_frame_->data() +
output_frame_->stride() * output_rect_.top() + output_frame_->stride() * output_rect.top() +
webrtc::DesktopFrame::kBytesPerPixel * output_rect_.left(); webrtc::DesktopFrame::kBytesPerPixel * output_rect.left();
libyuv::ARGBScale(frame->data(), frame->stride(), libyuv::ARGBScale(frame->data(), frame->stride(),
frame->size().width(), frame->size().height(), frame->size().width(), frame->size().height(),
output_rect_data, output_frame_->stride(), output_rect_data, output_frame_->stride(),
output_rect_.width(), output_rect_.height(), output_rect.width(), output_rect.height(),
libyuv::kFilterBilinear); libyuv::kFilterBilinear);
output_data = output_frame_->data(); output_data = output_frame_->data();
} else if (IsFrameUnpackedOrInverted(frame)) { } else if (IsFrameUnpackedOrInverted(frame)) {
...@@ -311,49 +311,14 @@ void DesktopCaptureDevice::Core::OnCaptureCompleted( ...@@ -311,49 +311,14 @@ void DesktopCaptureDevice::Core::OnCaptureCompleted(
} }
client_->OnIncomingCapturedData( client_->OnIncomingCapturedData(
output_data, output_bytes, capture_format_, 0, base::TimeTicks::Now()); output_data,
} output_bytes,
media::VideoCaptureFormat(gfx::Size(output_size.width(),
void DesktopCaptureDevice::Core::RefreshCaptureFormat( output_size.height()),
const webrtc::DesktopSize& frame_size) { requested_frame_rate_,
if (previous_frame_size_.equals(frame_size)) media::PIXEL_FORMAT_ARGB),
return; 0,
base::TimeTicks::Now());
// 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;
} }
void DesktopCaptureDevice::Core::OnCaptureTimer() { void DesktopCaptureDevice::Core::OnCaptureTimer() {
...@@ -375,7 +340,8 @@ void DesktopCaptureDevice::Core::CaptureFrameAndScheduleNext() { ...@@ -375,7 +340,8 @@ void DesktopCaptureDevice::Core::CaptureFrameAndScheduleNext() {
// Limit frame-rate to reduce CPU consumption. // Limit frame-rate to reduce CPU consumption.
base::TimeDelta capture_period = std::max( base::TimeDelta capture_period = std::max(
(last_capture_duration * 100) / kMaximumCpuConsumptionPercentage, (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. // Schedule a task for the next frame.
capture_timer_.Start(FROM_HERE, capture_period - last_capture_duration, capture_timer_.Start(FROM_HERE, capture_period - last_capture_duration,
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "content/browser/media/capture/desktop_capture_device.h" #include "content/browser/media/capture/desktop_capture_device.h"
#include <algorithm>
#include <string> #include <string>
#include "base/basictypes.h" #include "base/basictypes.h"
...@@ -22,8 +23,10 @@ using ::testing::_; ...@@ -22,8 +23,10 @@ using ::testing::_;
using ::testing::AnyNumber; using ::testing::AnyNumber;
using ::testing::DoAll; using ::testing::DoAll;
using ::testing::Expectation; using ::testing::Expectation;
using ::testing::Invoke;
using ::testing::InvokeWithoutArgs; using ::testing::InvokeWithoutArgs;
using ::testing::SaveArg; using ::testing::SaveArg;
using ::testing::WithArg;
namespace content { namespace content {
...@@ -33,10 +36,10 @@ MATCHER_P2(EqualsCaptureCapability, width, height, "") { ...@@ -33,10 +36,10 @@ MATCHER_P2(EqualsCaptureCapability, width, height, "") {
return arg.width == width && arg.height == height; return arg.width == width && arg.height == height;
} }
const int kTestFrameWidth1 = 100; const int kTestFrameWidth1 = 500;
const int kTestFrameHeight1 = 100; const int kTestFrameHeight1 = 500;
const int kTestFrameWidth2 = 200; const int kTestFrameWidth2 = 400;
const int kTestFrameHeight2 = 150; const int kTestFrameHeight2 = 300;
const int kFrameRate = 30; const int kFrameRate = 30;
...@@ -208,6 +211,32 @@ class FakeScreenCapturer : public webrtc::ScreenCapturer { ...@@ -208,6 +211,32 @@ class FakeScreenCapturer : public webrtc::ScreenCapturer {
bool generate_cropped_frames_; 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 } // namespace
class DesktopCaptureDeviceTest : public testing::Test { class DesktopCaptureDeviceTest : public testing::Test {
...@@ -277,15 +306,15 @@ TEST_F(DesktopCaptureDeviceTest, ScreenResolutionChangeConstantResolution) { ...@@ -277,15 +306,15 @@ TEST_F(DesktopCaptureDeviceTest, ScreenResolutionChangeConstantResolution) {
CreateScreenCaptureDevice(scoped_ptr<webrtc::DesktopCapturer>(mock_capturer)); 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); base::WaitableEvent done_event(false, false);
int frame_size;
scoped_ptr<MockDeviceClient> client(new MockDeviceClient()); scoped_ptr<MockDeviceClient> client(new MockDeviceClient());
EXPECT_CALL(*client, OnError(_)).Times(0); EXPECT_CALL(*client, OnError(_)).Times(0);
EXPECT_CALL(*client, OnIncomingCapturedData(_, _, _, _, _)).WillRepeatedly( EXPECT_CALL(*client, OnIncomingCapturedData(_, _, _, _, _)).WillRepeatedly(
DoAll(SaveArg<1>(&frame_size), DoAll(WithArg<2>(Invoke(&format_checker,
SaveArg<2>(&format), &FormatChecker::ExpectAcceptableSize)),
InvokeWithoutArgs(&done_event, &base::WaitableEvent::Signal))); InvokeWithoutArgs(&done_event, &base::WaitableEvent::Signal)));
media::VideoCaptureParams capture_params; media::VideoCaptureParams capture_params;
...@@ -293,23 +322,66 @@ TEST_F(DesktopCaptureDeviceTest, ScreenResolutionChangeConstantResolution) { ...@@ -293,23 +322,66 @@ TEST_F(DesktopCaptureDeviceTest, ScreenResolutionChangeConstantResolution) {
kTestFrameHeight1); kTestFrameHeight1);
capture_params.requested_format.frame_rate = kFrameRate; capture_params.requested_format.frame_rate = kFrameRate;
capture_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420; 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_device_->AllocateAndStart(capture_params, client.Pass());
// Capture at least two frames, to ensure that the source frame size has // Capture at least two frames, to ensure that the source frame size has
// changed while capturing. // changed to two different sizes while capturing. The mock for
EXPECT_TRUE(done_event.TimedWait(TestTimeouts::action_max_timeout())); // OnIncomingCapturedData() will use FormatChecker to examine the format of
done_event.Reset(); // each frame being delivered.
EXPECT_TRUE(done_event.TimedWait(TestTimeouts::action_max_timeout())); for (int i = 0; i < 2; ++i) {
EXPECT_TRUE(done_event.TimedWait(TestTimeouts::action_max_timeout()));
done_event.Reset();
}
capture_device_->StopAndDeAllocate(); capture_device_->StopAndDeAllocate();
}
EXPECT_EQ(kTestFrameWidth1, format.frame_size.width()); // Test that screen capturer behaves correctly if the source frame size changes,
EXPECT_EQ(kTestFrameHeight1, format.frame_size.height()); // where the video frames sent the the client vary in resolution but maintain
EXPECT_EQ(kFrameRate, format.frame_rate); // the same aspect ratio.
EXPECT_EQ(media::PIXEL_FORMAT_ARGB, format.pixel_format); 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 // Test that screen capturer behaves correctly if the source frame size changes
...@@ -319,38 +391,42 @@ TEST_F(DesktopCaptureDeviceTest, ScreenResolutionChangeVariableResolution) { ...@@ -319,38 +391,42 @@ TEST_F(DesktopCaptureDeviceTest, ScreenResolutionChangeVariableResolution) {
CreateScreenCaptureDevice(scoped_ptr<webrtc::DesktopCapturer>(mock_capturer)); 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); base::WaitableEvent done_event(false, false);
scoped_ptr<MockDeviceClient> client(new MockDeviceClient()); scoped_ptr<MockDeviceClient> client(new MockDeviceClient());
EXPECT_CALL(*client, OnError(_)).Times(0); EXPECT_CALL(*client, OnError(_)).Times(0);
EXPECT_CALL(*client, OnIncomingCapturedData(_, _, _, _, _)).WillRepeatedly( EXPECT_CALL(*client, OnIncomingCapturedData(_, _, _, _, _)).WillRepeatedly(
DoAll(SaveArg<2>(&format), DoAll(WithArg<2>(Invoke(&format_checker,
&FormatChecker::ExpectAcceptableSize)),
InvokeWithoutArgs(&done_event, &base::WaitableEvent::Signal))); InvokeWithoutArgs(&done_event, &base::WaitableEvent::Signal)));
media::VideoCaptureParams capture_params; media::VideoCaptureParams capture_params;
capture_params.requested_format.frame_size.SetSize(kTestFrameWidth2, const gfx::Size high_def_16_by_9(1920, 1080);
kTestFrameHeight2); 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.frame_rate = kFrameRate;
capture_params.requested_format.pixel_format = media::PIXEL_FORMAT_I420; 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_device_->AllocateAndStart(
capture_params, client.Pass()); capture_params, client.Pass());
// Capture at least three frames, to ensure that the source frame size has // Capture at least three frames, to ensure that the source frame size has
// changed at least twice while capturing. // changed to two different sizes while capturing. The mock for
EXPECT_TRUE(done_event.TimedWait(TestTimeouts::action_max_timeout())); // OnIncomingCapturedData() will use FormatChecker to examine the format of
done_event.Reset(); // each frame being delivered.
EXPECT_TRUE(done_event.TimedWait(TestTimeouts::action_max_timeout())); for (int i = 0; i < 3; ++i) {
done_event.Reset(); EXPECT_TRUE(done_event.TimedWait(TestTimeouts::action_max_timeout()));
EXPECT_TRUE(done_event.TimedWait(TestTimeouts::action_max_timeout())); done_event.Reset();
}
capture_device_->StopAndDeAllocate(); 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. // This test verifies that an unpacked frame is converted to a packed frame.
......
...@@ -91,7 +91,7 @@ class WebContentsAudioInputStream::Impl ...@@ -91,7 +91,7 @@ class WebContentsAudioInputStream::Impl
// Called by WebContentsTracker when the target of the audio mirroring has // Called by WebContentsTracker when the target of the audio mirroring has
// changed. // changed.
void OnTargetChanged(RenderWidgetHost* target); void OnTargetChanged(bool had_target);
// Injected dependencies. // Injected dependencies.
const int initial_render_process_id_; const int initial_render_process_id_;
...@@ -305,11 +305,10 @@ void WebContentsAudioInputStream::Impl::ReleaseInput( ...@@ -305,11 +305,10 @@ void WebContentsAudioInputStream::Impl::ReleaseInput(
delete stream; delete stream;
} }
void WebContentsAudioInputStream::Impl::OnTargetChanged( void WebContentsAudioInputStream::Impl::OnTargetChanged(bool had_target) {
RenderWidgetHost* target) {
DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(thread_checker_.CalledOnValidThread());
is_target_lost_ = !target; is_target_lost_ = !had_target;
if (state_ == MIRRORING) { if (state_ == MIRRORING) {
if (is_target_lost_) { if (is_target_lost_) {
......
...@@ -356,13 +356,7 @@ class WebContentsAudioInputStreamTest : public testing::Test { ...@@ -356,13 +356,7 @@ class WebContentsAudioInputStreamTest : public testing::Test {
private: private:
void SimulateChangeCallback(int render_process_id, int render_frame_id) { void SimulateChangeCallback(int render_process_id, int render_frame_id) {
ASSERT_FALSE(change_callback_.is_null()); ASSERT_FALSE(change_callback_.is_null());
if (render_process_id == -1 || render_frame_id == -1) { change_callback_.Run(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));
}
} }
scoped_ptr<TestBrowserThreadBundle> thread_bundle_; scoped_ptr<TestBrowserThreadBundle> thread_bundle_;
......
...@@ -44,6 +44,7 @@ void WebContentsTracker::Stop() { ...@@ -44,6 +44,7 @@ void WebContentsTracker::Stop() {
DCHECK(task_runner_->BelongsToCurrentThread()); DCHECK(task_runner_->BelongsToCurrentThread());
callback_.Reset(); callback_.Reset();
resize_callback_.Reset();
if (BrowserThread::CurrentlyOn(BrowserThread::UI)) { if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
WebContentsObserver::Observe(NULL); WebContentsObserver::Observe(NULL);
...@@ -78,6 +79,12 @@ RenderWidgetHost* WebContentsTracker::GetTargetRenderWidgetHost() const { ...@@ -78,6 +79,12 @@ RenderWidgetHost* WebContentsTracker::GetTargetRenderWidgetHost() const {
return rwh; 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) { void WebContentsTracker::OnPossibleTargetChange(bool force_callback_run) {
DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK_CURRENTLY_ON(BrowserThread::UI);
...@@ -89,19 +96,29 @@ void WebContentsTracker::OnPossibleTargetChange(bool force_callback_run) { ...@@ -89,19 +96,29 @@ void WebContentsTracker::OnPossibleTargetChange(bool force_callback_run) {
last_target_ = rwh; last_target_ = rwh;
if (task_runner_->BelongsToCurrentThread()) { if (task_runner_->BelongsToCurrentThread()) {
MaybeDoCallback(rwh); MaybeDoCallback(rwh != nullptr);
} else { return;
task_runner_->PostTask(
FROM_HERE,
base::Bind(&WebContentsTracker::MaybeDoCallback, this, rwh));
} }
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()); DCHECK(task_runner_->BelongsToCurrentThread());
if (!callback_.is_null()) 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, void WebContentsTracker::StartObservingWebContents(int render_process_id,
...@@ -128,6 +145,19 @@ void WebContentsTracker::RenderFrameHostChanged(RenderFrameHost* old_host, ...@@ -128,6 +145,19 @@ void WebContentsTracker::RenderFrameHostChanged(RenderFrameHost* old_host,
OnPossibleTargetChange(false); 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() { void WebContentsTracker::WebContentsDestroyed() {
Observe(NULL); Observe(NULL);
OnPossibleTargetChange(false); OnPossibleTargetChange(false);
......
...@@ -40,15 +40,15 @@ class CONTENT_EXPORT WebContentsTracker ...@@ -40,15 +40,15 @@ class CONTENT_EXPORT WebContentsTracker
explicit WebContentsTracker(bool track_fullscreen_rwh); explicit WebContentsTracker(bool track_fullscreen_rwh);
// Callback to indicate a new RenderWidgetHost should be targeted for capture. // 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). // (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 // Start tracking. The last-known |render_process_id| and
// |main_render_frame_id| are provided, and |callback| will be run once to // |main_render_frame_id| are provided, and |callback| will be run once to
// indicate the current capture target (this may occur during the invocation // indicate whether tracking successfully started (this may occur during the
// of Start(), or in the future). The callback will be invoked on the same // invocation of Start(), or in the future). The callback will be invoked on
// thread calling Start(). // the same thread calling Start().
virtual void Start(int render_process_id, int main_render_frame_id, virtual void Start(int render_process_id, int main_render_frame_id,
const ChangeCallback& callback); const ChangeCallback& callback);
...@@ -59,6 +59,12 @@ class CONTENT_EXPORT WebContentsTracker ...@@ -59,6 +59,12 @@ class CONTENT_EXPORT WebContentsTracker
// Current target. This must only be called on the UI BrowserThread. // Current target. This must only be called on the UI BrowserThread.
RenderWidgetHost* GetTargetRenderWidgetHost() const; 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: protected:
friend class base::RefCountedThreadSafe<WebContentsTracker>; friend class base::RefCountedThreadSafe<WebContentsTracker>;
~WebContentsTracker() override; ~WebContentsTracker() override;
...@@ -72,7 +78,12 @@ class CONTENT_EXPORT WebContentsTracker ...@@ -72,7 +78,12 @@ class CONTENT_EXPORT WebContentsTracker
// Called on the thread that Start()/Stop() are called on. Checks whether the // Called on the thread that Start()/Stop() are called on. Checks whether the
// callback is still valid and, if so, runs it. // 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 // Look-up the current WebContents instance associated with the given
// |render_process_id| and |main_render_frame_id| and begin observing it. // |render_process_id| and |main_render_frame_id| and begin observing it.
...@@ -86,6 +97,10 @@ class CONTENT_EXPORT WebContentsTracker ...@@ -86,6 +97,10 @@ class CONTENT_EXPORT WebContentsTracker
void RenderFrameHostChanged(RenderFrameHost* old_host, void RenderFrameHostChanged(RenderFrameHost* old_host,
RenderFrameHost* new_host) override; 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 // WebContentsObserver override to notify the client that the capture target
// has been permanently lost. // has been permanently lost.
void WebContentsDestroyed() override; void WebContentsDestroyed() override;
...@@ -109,6 +124,9 @@ class CONTENT_EXPORT WebContentsTracker ...@@ -109,6 +124,9 @@ class CONTENT_EXPORT WebContentsTracker
// This is used to eliminate duplicate callback runs. // This is used to eliminate duplicate callback runs.
RenderWidgetHost* last_target_; RenderWidgetHost* last_target_;
// Callback to run when the target RenderWidgetHost has resized.
base::Closure resize_callback_;
DISALLOW_COPY_AND_ASSIGN(WebContentsTracker); DISALLOW_COPY_AND_ASSIGN(WebContentsTracker);
}; };
......
...@@ -79,7 +79,6 @@ ...@@ -79,7 +79,6 @@
#include "third_party/skia/include/core/SkBitmap.h" #include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h" #include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/display.h" #include "ui/gfx/display.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/size_conversions.h" #include "ui/gfx/geometry/size_conversions.h"
#include "ui/gfx/screen.h" #include "ui/gfx/screen.h"
...@@ -87,20 +86,6 @@ namespace content { ...@@ -87,20 +86,6 @@ namespace content {
namespace { 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, void DeleteOnWorkerThread(scoped_ptr<base::Thread> render_thread,
const base::Closure& callback) { const base::Closure& callback) {
render_thread.reset(); render_thread.reset();
...@@ -254,8 +239,12 @@ class WebContentsCaptureMachine : public VideoCaptureMachine { ...@@ -254,8 +239,12 @@ class WebContentsCaptureMachine : public VideoCaptureMachine {
deliver_frame_cb, deliver_frame_cb,
bool success); bool success);
// Remove the old subscription, and start a new one if |rwh| is not NULL. // Remove the old subscription, and attempt to start a new one if |had_target|
void RenewFrameSubscription(RenderWidgetHost* rwh); // is true.
void RenewFrameSubscription(bool had_target);
// Called whenever the render widget is resized.
void UpdateCaptureSize();
// Parameters saved in constructor. // Parameters saved in constructor.
const int initial_render_process_id_; const int initial_render_process_id_;
...@@ -396,7 +385,7 @@ void RenderVideoFrame(const SkBitmap& input, ...@@ -396,7 +385,7 @@ void RenderVideoFrame(const SkBitmap& input,
// Calculate the width and height of the content region in the |output|, based // Calculate the width and height of the content region in the |output|, based
// on the aspect ratio of |input|. // 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())); output->visible_rect(), gfx::Size(input.width(), input.height()));
// Scale the bitmap to the required size, if necessary. // Scale the bitmap to the required size, if necessary.
...@@ -425,12 +414,20 @@ void RenderVideoFrame(const SkBitmap& input, ...@@ -425,12 +414,20 @@ void RenderVideoFrame(const SkBitmap& input,
TRACE_EVENT_ASYNC_STEP_INTO0("gpu.capture", "Capture", output.get(), "YUV"); 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( media::CopyRGBToVideoFrame(
reinterpret_cast<uint8*>(scaled_bitmap.getPixels()), reinterpret_cast<uint8*>(scaled_bitmap.getPixels()),
scaled_bitmap.rowBytes(), scaled_bitmap.rowBytes(),
region_in_frame, region_in_yv12_frame,
output.get()); output.get());
} }
...@@ -501,6 +498,9 @@ bool WebContentsCaptureMachine::Start( ...@@ -501,6 +498,9 @@ bool WebContentsCaptureMachine::Start(
// Note: Creation of the first WeakPtr in the following statement will cause // Note: Creation of the first WeakPtr in the following statement will cause
// IsStarted() to return true from now on. // 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_, tracker_->Start(initial_render_process_id_, initial_main_render_frame_id_,
base::Bind(&WebContentsCaptureMachine::RenewFrameSubscription, base::Bind(&WebContentsCaptureMachine::RenewFrameSubscription,
weak_ptr_factory_.GetWeakPtr())); weak_ptr_factory_.GetWeakPtr()));
...@@ -522,7 +522,7 @@ void WebContentsCaptureMachine::Stop(const base::Closure& callback) { ...@@ -522,7 +522,7 @@ void WebContentsCaptureMachine::Stop(const base::Closure& callback) {
// Note: RenewFrameSubscription() must be called before stopping |tracker_| so // Note: RenewFrameSubscription() must be called before stopping |tracker_| so
// the web_contents() can be notified that the capturing is ending. // the web_contents() can be notified that the capturing is ending.
RenewFrameSubscription(NULL); RenewFrameSubscription(false);
tracker_->Stop(); tracker_->Stop();
// The render thread cannot be stopped on the UI thread, so post a message // The render thread cannot be stopped on the UI thread, so post a message
...@@ -551,11 +551,6 @@ void WebContentsCaptureMachine::Capture( ...@@ -551,11 +551,6 @@ void WebContentsCaptureMachine::Capture(
} }
gfx::Size view_size = view->GetViewBounds().size(); 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_) { if (view_size != last_view_size_) {
last_view_size_ = view_size; last_view_size_ = view_size;
...@@ -574,6 +569,8 @@ void WebContentsCaptureMachine::Capture( ...@@ -574,6 +569,8 @@ void WebContentsCaptureMachine::Capture(
weak_ptr_factory_.GetWeakPtr(), weak_ptr_factory_.GetWeakPtr(),
start_time, deliver_frame_cb)); start_time, deliver_frame_cb));
} else { } else {
const gfx::Size fitted_size = view_size.IsEmpty() ? gfx::Size() :
media::ComputeLetterboxRegion(target->visible_rect(), view_size).size();
rwh->CopyFromBackingStore( rwh->CopyFromBackingStore(
gfx::Rect(), gfx::Rect(),
fitted_size, // Size here is a request not always honored. fitted_size, // Size here is a request not always honored.
...@@ -589,7 +586,10 @@ void WebContentsCaptureMachine::Capture( ...@@ -589,7 +586,10 @@ void WebContentsCaptureMachine::Capture(
gfx::Size WebContentsCaptureMachine::ComputeOptimalTargetSize() const { gfx::Size WebContentsCaptureMachine::ComputeOptimalTargetSize() const {
DCHECK_CURRENTLY_ON(BrowserThread::UI); 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, // 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 // shrink |optimal_size| by that amount. Then, when external code resizes the
...@@ -657,9 +657,12 @@ void WebContentsCaptureMachine::DidCopyFromCompositingSurfaceToVideoFrame( ...@@ -657,9 +657,12 @@ void WebContentsCaptureMachine::DidCopyFromCompositingSurfaceToVideoFrame(
deliver_frame_cb.Run(start_time, success); deliver_frame_cb.Run(start_time, success);
} }
void WebContentsCaptureMachine::RenewFrameSubscription(RenderWidgetHost* rwh) { void WebContentsCaptureMachine::RenewFrameSubscription(bool had_target) {
DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK_CURRENTLY_ON(BrowserThread::UI);
RenderWidgetHost* const rwh =
had_target ? tracker_->GetTargetRenderWidgetHost() : nullptr;
// Always destroy the old subscription before creating a new one. // Always destroy the old subscription before creating a new one.
const bool had_subscription = !!subscription_; const bool had_subscription = !!subscription_;
subscription_.reset(); subscription_.reset();
...@@ -688,6 +691,18 @@ void WebContentsCaptureMachine::RenewFrameSubscription(RenderWidgetHost* rwh) { ...@@ -688,6 +691,18 @@ void WebContentsCaptureMachine::RenewFrameSubscription(RenderWidgetHost* rwh) {
weak_ptr_factory_.GetWeakPtr()))); 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 } // namespace
WebContentsVideoCaptureDevice::WebContentsVideoCaptureDevice( WebContentsVideoCaptureDevice::WebContentsVideoCaptureDevice(
......
...@@ -973,6 +973,8 @@ ...@@ -973,6 +973,8 @@
'browser/media/capture/animated_content_sampler.h', 'browser/media/capture/animated_content_sampler.h',
'browser/media/capture/audio_mirroring_manager.cc', 'browser/media/capture/audio_mirroring_manager.cc',
'browser/media/capture/audio_mirroring_manager.h', '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.cc',
'browser/media/capture/content_video_capture_device_core.h', 'browser/media/capture/content_video_capture_device_core.h',
'browser/media/capture/feedback_signal_accumulator.cc', 'browser/media/capture/feedback_signal_accumulator.cc',
......
...@@ -471,6 +471,7 @@ ...@@ -471,6 +471,7 @@
'browser/media/audio_stream_monitor_unittest.cc', 'browser/media/audio_stream_monitor_unittest.cc',
'browser/media/capture/animated_content_sampler_unittest.cc', 'browser/media/capture/animated_content_sampler_unittest.cc',
'browser/media/capture/audio_mirroring_manager_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/feedback_signal_accumulator_unittest.cc',
'browser/media/capture/smooth_event_sampler_unittest.cc', 'browser/media/capture/smooth_event_sampler_unittest.cc',
'browser/media/capture/video_capture_oracle_unittest.cc', 'browser/media/capture/video_capture_oracle_unittest.cc',
......
...@@ -270,6 +270,23 @@ void RotatePlaneByPixels( ...@@ -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, gfx::Rect ComputeLetterboxRegion(const gfx::Rect& bounds,
const gfx::Size& content) { const gfx::Size& content) {
// If |content| has an undefined aspect ratio, let's not try to divide by // 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, ...@@ -277,19 +294,33 @@ gfx::Rect ComputeLetterboxRegion(const gfx::Rect& bounds,
if (content.IsEmpty()) if (content.IsEmpty())
return gfx::Rect(); 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; gfx::Rect result = bounds;
result.ClampToCenteredSize(letterbox); result.ClampToCenteredSize(ScaleSizeToTarget(content, bounds.size(), true));
return result; 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, void CopyRGBToVideoFrame(const uint8* source,
int stride, int stride,
const gfx::Rect& region_in_frame, const gfx::Rect& region_in_frame,
......
...@@ -84,6 +84,29 @@ MEDIA_EXPORT void RotatePlaneByPixels( ...@@ -84,6 +84,29 @@ MEDIA_EXPORT void RotatePlaneByPixels(
MEDIA_EXPORT gfx::Rect ComputeLetterboxRegion(const gfx::Rect& bounds, MEDIA_EXPORT gfx::Rect ComputeLetterboxRegion(const gfx::Rect& bounds,
const gfx::Size& content); 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. // Copy an RGB bitmap into the specified |region_in_frame| of a YUV video frame.
// Fills the regions outside |region_in_frame| with black. // Fills the regions outside |region_in_frame| with black.
MEDIA_EXPORT void CopyRGBToVideoFrame(const uint8* source, MEDIA_EXPORT void CopyRGBToVideoFrame(const uint8* source,
......
...@@ -328,6 +328,8 @@ TEST_P(VideoUtilRotationTest, Rotate) { ...@@ -328,6 +328,8 @@ TEST_P(VideoUtilRotationTest, Rotate) {
INSTANTIATE_TEST_CASE_P(, VideoUtilRotationTest, INSTANTIATE_TEST_CASE_P(, VideoUtilRotationTest,
testing::ValuesIn(kVideoRotationTestData)); testing::ValuesIn(kVideoRotationTestData));
// Tests the ComputeLetterboxRegion function. Also, because of shared code
// internally, this also tests ScaleSizeToFitWithinTarget().
TEST_F(VideoUtilTest, ComputeLetterboxRegion) { TEST_F(VideoUtilTest, ComputeLetterboxRegion) {
EXPECT_EQ(gfx::Rect(167, 0, 666, 500), EXPECT_EQ(gfx::Rect(167, 0, 666, 500),
ComputeLetterboxRegion(gfx::Rect(0, 0, 1000, 500), ComputeLetterboxRegion(gfx::Rect(0, 0, 1000, 500),
...@@ -348,6 +350,48 @@ TEST_F(VideoUtilTest, ComputeLetterboxRegion) { ...@@ -348,6 +350,48 @@ TEST_F(VideoUtilTest, ComputeLetterboxRegion) {
gfx::Size(0, 0)).IsEmpty()); 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) { TEST_F(VideoUtilTest, LetterboxYUV) {
int width = 40; int width = 40;
int height = 30; int height = 30;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment