|  | /* | 
|  | *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. | 
|  | * | 
|  | *  Use of this source code is governed by a BSD-style license | 
|  | *  that can be found in the LICENSE file in the root of the source | 
|  | *  tree. An additional intellectual property rights grant can be found | 
|  | *  in the file PATENTS.  All contributing project authors may | 
|  | *  be found in the AUTHORS file in the root of the source tree. | 
|  | */ | 
|  |  | 
|  | #include "video/alignment_adjuster.h" | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <cstddef> | 
|  | #include <cstdlib> | 
|  | #include <limits> | 
|  | #include <optional> | 
|  |  | 
|  | #include "absl/algorithm/container.h" | 
|  | #include "api/video_codecs/video_encoder.h" | 
|  | #include "rtc_base/logging.h" | 
|  | #include "video/config/video_encoder_config.h" | 
|  |  | 
|  | namespace webrtc { | 
|  | namespace { | 
|  | // Round each scale factor to the closest rational in form alignment/i where i | 
|  | // is a multiple of `requested_alignment`. Each resolution divisible by | 
|  | // `alignment` will be divisible by `requested_alignment` after the scale factor | 
|  | // is applied. | 
|  | double RoundToMultiple(int alignment, | 
|  | int requested_alignment, | 
|  | VideoEncoderConfig* config, | 
|  | bool update_config) { | 
|  | double diff = 0.0; | 
|  | for (auto& layer : config->simulcast_layers) { | 
|  | double min_dist = std::numeric_limits<double>::max(); | 
|  | double new_scale = 1.0; | 
|  | for (int i = requested_alignment; i <= alignment; | 
|  | i += requested_alignment) { | 
|  | double dist = std::abs(layer.scale_resolution_down_by - | 
|  | alignment / static_cast<double>(i)); | 
|  | if (dist <= min_dist) { | 
|  | min_dist = dist; | 
|  | new_scale = alignment / static_cast<double>(i); | 
|  | } | 
|  | } | 
|  | diff += std::abs(layer.scale_resolution_down_by - new_scale); | 
|  | if (update_config) { | 
|  | RTC_LOG(LS_INFO) << "scale_resolution_down_by " | 
|  | << layer.scale_resolution_down_by << " -> " << new_scale; | 
|  | layer.scale_resolution_down_by = new_scale; | 
|  | } | 
|  | } | 
|  | return diff; | 
|  | } | 
|  | }  // namespace | 
|  |  | 
|  | // Input: encoder_info.requested_resolution_alignment (K) | 
|  | // Input: encoder_info.apply_alignment_to_all_simulcast_layers (B) | 
|  | // Input: vector config->simulcast_layers.scale_resolution_down_by (S[i]) | 
|  | // Output: | 
|  | // If B is false, returns K and does not adjust scaling factors. | 
|  | // Otherwise, returns adjusted alignment (A), adjusted scaling factors (S'[i]) | 
|  | // are written in `config` such that: | 
|  | // | 
|  | // A / S'[i] are integers divisible by K | 
|  | // sum abs(S'[i] - S[i]) -> min | 
|  | // A integer <= 16 | 
|  | // | 
|  | // Solution chooses closest S'[i] in a form A / j where j is a multiple of K. | 
|  |  | 
|  | int AlignmentAdjuster::GetAlignmentAndMaybeAdjustScaleFactors( | 
|  | const VideoEncoder::EncoderInfo& encoder_info, | 
|  | VideoEncoderConfig* config, | 
|  | std::optional<size_t> max_layers) { | 
|  | const int requested_alignment = encoder_info.requested_resolution_alignment; | 
|  | if (!encoder_info.apply_alignment_to_all_simulcast_layers) { | 
|  | return requested_alignment; | 
|  | } | 
|  |  | 
|  | if (requested_alignment < 1 || config->number_of_streams <= 1 || | 
|  | config->simulcast_layers.size() <= 1) { | 
|  | return requested_alignment; | 
|  | } | 
|  |  | 
|  | // Update alignment to also apply to simulcast layers. | 
|  | const bool has_scale_resolution_down_by = | 
|  | absl::c_any_of(config->simulcast_layers, [](const VideoStream& layer) { | 
|  | return layer.scale_resolution_down_by >= 1.0; | 
|  | }); | 
|  |  | 
|  | if (!has_scale_resolution_down_by) { | 
|  | // Default resolution downscaling used (scale factors: 1, 2, 4, ...). | 
|  | size_t size = config->simulcast_layers.size(); | 
|  | if (max_layers && *max_layers > 0 && *max_layers < size) { | 
|  | size = *max_layers; | 
|  | } | 
|  | return requested_alignment * (1 << (size - 1)); | 
|  | } | 
|  |  | 
|  | // Get alignment for downscaled layers. | 
|  | // Adjust `scale_resolution_down_by` to a common multiple to limit the | 
|  | // alignment value (to avoid largely cropped frames and possibly with an | 
|  | // aspect ratio far from the original). | 
|  | const int kMaxAlignment = 16; | 
|  |  | 
|  | for (auto& layer : config->simulcast_layers) { | 
|  | layer.scale_resolution_down_by = | 
|  | std::max(layer.scale_resolution_down_by, 1.0); | 
|  | layer.scale_resolution_down_by = | 
|  | std::min(layer.scale_resolution_down_by, 10000.0); | 
|  | } | 
|  |  | 
|  | // Decide on common multiple to use. | 
|  | double min_diff = std::numeric_limits<double>::max(); | 
|  | int best_alignment = 1; | 
|  | for (int alignment = requested_alignment; alignment <= kMaxAlignment; | 
|  | ++alignment) { | 
|  | double diff = RoundToMultiple(alignment, requested_alignment, config, | 
|  | /*update_config=*/false); | 
|  | if (diff < min_diff) { | 
|  | min_diff = diff; | 
|  | best_alignment = alignment; | 
|  | } | 
|  | } | 
|  | RoundToMultiple(best_alignment, requested_alignment, config, | 
|  | /*update_config=*/true); | 
|  |  | 
|  | return std::max(best_alignment, requested_alignment); | 
|  | } | 
|  | }  // namespace webrtc |