Skip to content

Commit

Permalink
Implement all resolution change policies for desktop and tab capture.
Browse files Browse the repository at this point in the history
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}
(cherry picked from commit 39254e6)

Review URL: https://codereview.chromium.org/1133093005

Cr-Commit-Position: refs/branch-heads/2403@{crosswalk-project#22}
Cr-Branched-From: f54b809-refs/heads/master@{#330231}
  • Loading branch information
miu-chromium committed May 19, 2015
1 parent b37fa6f commit 78b9ca2
Show file tree
Hide file tree
Showing 18 changed files with 972 additions and 246 deletions.
120 changes: 120 additions & 0 deletions content/browser/media/capture/capture_resolution_chooser.cc
Original file line number Diff line number Diff line change
@@ -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
59 changes: 59 additions & 0 deletions content/browser/media/capture/capture_resolution_chooser.h
Original file line number Diff line number Diff line change
@@ -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_
169 changes: 169 additions & 0 deletions content/browser/media/capture/capture_resolution_chooser_unittest.cc
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 78b9ca2

Please sign in to comment.