capture_resolution_chooser.cc 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. // Copyright 2015 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "media/capture/content/capture_resolution_chooser.h"
  5. #include <algorithm>
  6. #include <limits>
  7. #include "base/logging.h"
  8. #include "base/strings/string_util.h"
  9. #include "media/base/video_util.h"
  10. namespace media {
  11. namespace {
  12. // Each snapped frame size is an integer multiple of this many lines apart.
  13. // This is ideal for 16:9 content, but seems to also work well for many
  14. // arbitrary aspect ratios.
  15. const int kSnappedHeightStep = 90;
  16. // The minimum amount of decrease in area between consecutive snapped frame
  17. // sizes. This matters externally, where the end-to-end system is hunting for a
  18. // capture size that works within all resource bottlenecks. If the snapped
  19. // frame sizes are too-close together, the end-to-end system cannot stablize.
  20. // If they are too-far apart, quality is being sacrificed.
  21. const int kMinAreaDecreasePercent = 15;
  22. // Returns |size|, unless it exceeds |max_size| or is under |min_size|. When
  23. // the bounds are exceeded, computes and returns an alternate size of similar
  24. // aspect ratio that is within the bounds.
  25. gfx::Size ComputeBoundedCaptureSize(const gfx::Size& size,
  26. const gfx::Size& min_size,
  27. const gfx::Size& max_size) {
  28. if (size.width() > max_size.width() || size.height() > max_size.height()) {
  29. gfx::Size result = ScaleSizeToFitWithinTarget(size, max_size);
  30. result.SetToMax(min_size);
  31. return result;
  32. } else if (size.width() < min_size.width() ||
  33. size.height() < min_size.height()) {
  34. gfx::Size result = ScaleSizeToEncompassTarget(size, min_size);
  35. result.SetToMin(max_size);
  36. return result;
  37. } else {
  38. return size;
  39. }
  40. }
  41. // Returns true if the area of |a| is less than that of |b|.
  42. bool CompareByArea(const gfx::Size& a, const gfx::Size& b) {
  43. return a.GetArea() < b.GetArea();
  44. }
  45. } // namespace
  46. // static
  47. constexpr gfx::Size CaptureResolutionChooser::kDefaultCaptureSize;
  48. CaptureResolutionChooser::CaptureResolutionChooser()
  49. : min_frame_size_(kDefaultCaptureSize),
  50. max_frame_size_(kDefaultCaptureSize),
  51. apply_aspect_ratio_adjustment_(false),
  52. target_area_(std::numeric_limits<decltype(target_area_)>::max()),
  53. capture_size_(kDefaultCaptureSize),
  54. snapped_sizes_({kDefaultCaptureSize}) {}
  55. CaptureResolutionChooser::~CaptureResolutionChooser() = default;
  56. void CaptureResolutionChooser::SetConstraints(const gfx::Size& min_frame_size,
  57. const gfx::Size& max_frame_size,
  58. bool use_fixed_aspect_ratio) {
  59. DCHECK_LT(0, min_frame_size.width());
  60. DCHECK_LT(0, min_frame_size.height());
  61. DCHECK_LE(min_frame_size.width(), max_frame_size.width());
  62. DCHECK_LE(min_frame_size.height(), max_frame_size.height());
  63. min_frame_size_ = min_frame_size;
  64. max_frame_size_ = max_frame_size;
  65. apply_aspect_ratio_adjustment_ =
  66. (min_frame_size != max_frame_size && use_fixed_aspect_ratio);
  67. UpdateSnappedFrameSizes();
  68. RecomputeCaptureSize();
  69. }
  70. void CaptureResolutionChooser::SetSourceSize(const gfx::Size& source_size) {
  71. source_size_ = source_size;
  72. UpdateSnappedFrameSizes();
  73. RecomputeCaptureSize();
  74. }
  75. void CaptureResolutionChooser::SetTargetFrameArea(int area) {
  76. DCHECK_GE(area, 0);
  77. target_area_ = area;
  78. RecomputeCaptureSize();
  79. }
  80. gfx::Size CaptureResolutionChooser::FindNearestFrameSize(int area) const {
  81. const auto begin = snapped_sizes_.begin();
  82. const auto end = snapped_sizes_.end();
  83. DCHECK(begin != end);
  84. const gfx::Size area_as_size(area, 1); // A facade for CompareByArea().
  85. const auto p = std::lower_bound(begin, end, area_as_size, &CompareByArea);
  86. if (p == end) {
  87. // Boundary case: The target |area| is greater than or equal to the
  88. // largest, so the largest size is closest.
  89. return *(end - 1);
  90. } else if (p == begin) {
  91. // Boundary case: The target |area| is smaller than the smallest, so the
  92. // smallest size is closest.
  93. return *begin;
  94. } else {
  95. // |p| points to the smallest size whose area is greater than or equal to
  96. // the target |area|. The next smaller size could be closer to the target
  97. // |area|, so it must also be considered.
  98. const auto q = p - 1;
  99. return ((p->GetArea() - area) < (area - q->GetArea())) ? *p : *q;
  100. }
  101. }
  102. gfx::Size CaptureResolutionChooser::FindLargerFrameSize(
  103. int area,
  104. int num_steps_up) const {
  105. DCHECK_GT(num_steps_up, 0);
  106. const auto begin = snapped_sizes_.begin();
  107. const auto end = snapped_sizes_.end();
  108. DCHECK(begin != end);
  109. const gfx::Size area_as_size(area, 1); // A facade for CompareByArea().
  110. auto p = std::upper_bound(begin, end, area_as_size, &CompareByArea);
  111. // |p| is already pointing one step up.
  112. const int additional_steps_up = num_steps_up - 1;
  113. if ((end - p) > additional_steps_up)
  114. return *(p + additional_steps_up);
  115. else
  116. return *(end - 1);
  117. }
  118. gfx::Size CaptureResolutionChooser::FindSmallerFrameSize(
  119. int area,
  120. int num_steps_down) const {
  121. DCHECK_GT(num_steps_down, 0);
  122. const auto begin = snapped_sizes_.begin();
  123. const auto end = snapped_sizes_.end();
  124. DCHECK(begin != end);
  125. const gfx::Size area_as_size(area, 1); // A facade for CompareByArea().
  126. const auto p = std::lower_bound(begin, end, area_as_size, &CompareByArea);
  127. if ((p - begin) >= num_steps_down)
  128. return *(p - num_steps_down);
  129. else
  130. return *begin;
  131. }
  132. void CaptureResolutionChooser::RecomputeCaptureSize() {
  133. const gfx::Size old_capture_size = capture_size_;
  134. capture_size_ = FindNearestFrameSize(target_area_);
  135. VLOG_IF(1, capture_size_ != old_capture_size)
  136. << "Recomputed capture size from " << old_capture_size.ToString()
  137. << " to " << capture_size_.ToString() << " ("
  138. << (100.0 * capture_size_.height() / snapped_sizes_.back().height())
  139. << "% of ideal size)";
  140. }
  141. void CaptureResolutionChooser::UpdateSnappedFrameSizes() {
  142. // Compute the largest snapped frame size, and the one that will determine the
  143. // aspect ratio of all the snapped frame sizes. If the |source_size_| has not
  144. // yet been set, use the |capture_size_| as a substitute.
  145. gfx::Size constrained_size =
  146. source_size_.IsEmpty() ? capture_size_ : source_size_;
  147. constrained_size = ComputeBoundedCaptureSize(
  148. apply_aspect_ratio_adjustment_
  149. ? PadToMatchAspectRatio(constrained_size, max_frame_size_)
  150. : constrained_size,
  151. min_frame_size_, max_frame_size_);
  152. // Start with the constrained size as the largest in the set.
  153. snapped_sizes_.clear();
  154. snapped_sizes_.push_back(constrained_size);
  155. // Repeatedly decrease the size in steps, adding each to |snapped_sizes_|.
  156. // However, skip the sizes that do not decrease in area by enough, relative to
  157. // the prior size.
  158. int last_area = constrained_size.GetArea();
  159. for (int height = constrained_size.height() - kSnappedHeightStep;
  160. height >= min_frame_size_.height(); height -= kSnappedHeightStep) {
  161. const int width =
  162. height * constrained_size.width() / constrained_size.height();
  163. if (width < min_frame_size_.width())
  164. break;
  165. const int smaller_area = width * height;
  166. const int percent_decrease = 100 * (last_area - smaller_area) / last_area;
  167. if (percent_decrease >= kMinAreaDecreasePercent) {
  168. snapped_sizes_.push_back(gfx::Size(width, height));
  169. last_area = smaller_area;
  170. }
  171. }
  172. // Reverse ordering, so that sizes are from smallest to largest.
  173. std::reverse(snapped_sizes_.begin(), snapped_sizes_.end());
  174. if (VLOG_IS_ON(1)) {
  175. std::vector<std::string> stringified_sizes;
  176. for (const gfx::Size& size : snapped_sizes_)
  177. stringified_sizes.push_back(size.ToString());
  178. VLOG_STREAM(1) << "Recomputed snapped frame sizes: "
  179. << base::JoinString(stringified_sizes, " <--> ");
  180. }
  181. }
  182. } // namespace media