| /* | |
| * Copyright (c) 2022 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/config/encoder_stream_factory.h" | |
| #include <algorithm> | |
| #include <cmath> | |
| #include <cstddef> | |
| #include <limits> | |
| #include <numeric> | |
| #include <optional> | |
| #include <vector> | |
| #include "absl/algorithm/container.h" | |
| #include "api/field_trials_view.h" | |
| #include "api/units/data_rate.h" | |
| #include "api/video/resolution.h" | |
| #include "api/video/video_codec_type.h" | |
| #include "api/video/video_source_interface.h" | |
| #include "api/video_codecs/spatial_layer.h" | |
| #include "api/video_codecs/video_codec.h" | |
| #include "api/video_codecs/video_encoder.h" | |
| #include "call/adaptation/video_source_restrictions.h" | |
| #include "media/base/media_constants.h" | |
| #include "media/base/video_adapter.h" | |
| #include "modules/video_coding/codecs/vp9/svc_config.h" | |
| #include "modules/video_coding/utility/simulcast_utility.h" | |
| #include "rtc_base/checks.h" | |
| #include "rtc_base/experiments/min_video_bitrate_experiment.h" | |
| #include "rtc_base/experiments/normalize_simulcast_size_experiment.h" | |
| #include "rtc_base/logging.h" | |
| #include "rtc_base/numerics/safe_conversions.h" | |
| #include "video/config/simulcast.h" | |
| #include "video/config/video_encoder_config.h" | |
| namespace webrtc { | |
| namespace { | |
| constexpr int kMinLayerSize = 16; | |
| Resolution ScaleResolutionDownBy(const Resolution& resolution, | |
| double scale_down_by) { | |
| if (resolution.width <= kMinLayerSize || resolution.height <= kMinLayerSize) { | |
| return resolution; | |
| } | |
| int short_side = std::min(resolution.width / scale_down_by, | |
| resolution.height / scale_down_by); | |
| if (short_side < kMinLayerSize) { | |
| // Adjust scale factor to make the short side equal to kMinLayerSize. | |
| scale_down_by = | |
| std::min(resolution.width / static_cast<double>(kMinLayerSize), | |
| resolution.height / static_cast<double>(kMinLayerSize)); | |
| } | |
| return { | |
| .width = static_cast<int>(std::round(resolution.width / scale_down_by)), | |
| .height = | |
| static_cast<int>(std::round(resolution.height / scale_down_by))}; | |
| } | |
| bool PowerOfTwo(int value) { | |
| return (value > 0) && ((value & (value - 1)) == 0); | |
| } | |
| bool IsScaleFactorsPowerOfTwo(const VideoEncoderConfig& config) { | |
| for (const auto& layer : config.simulcast_layers) { | |
| double scale = std::max(layer.scale_resolution_down_by, 1.0); | |
| if (std::round(scale) != scale || !PowerOfTwo(scale)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| bool IsTemporalLayersSupported(VideoCodecType codec_type) { | |
| return codec_type == VideoCodecType::kVideoCodecVP8 || | |
| codec_type == VideoCodecType::kVideoCodecVP9 || | |
| codec_type == VideoCodecType::kVideoCodecAV1 || | |
| codec_type == VideoCodecType::kVideoCodecH265; | |
| } | |
| size_t FindRequiredActiveLayers(const VideoEncoderConfig& encoder_config) { | |
| // Need enough layers so that at least the first active one is present. | |
| for (size_t i = 0; i < encoder_config.number_of_streams; ++i) { | |
| if (encoder_config.simulcast_layers[i].active) { | |
| return i + 1; | |
| } | |
| } | |
| return 0; | |
| } | |
| // The selected thresholds for QVGA and VGA corresponded to a QP around 10. | |
| // The change in QP declined above the selected bitrates. | |
| int GetMaxDefaultVideoBitrateKbps(int width, int height, bool is_screenshare) { | |
| int max_bitrate; | |
| if (width * height <= 320 * 240) { | |
| max_bitrate = 600; | |
| } else if (width * height <= 640 * 480) { | |
| max_bitrate = 1700; | |
| } else if (width * height <= 960 * 540) { | |
| max_bitrate = 2000; | |
| } else { | |
| max_bitrate = 2500; | |
| } | |
| if (is_screenshare) | |
| max_bitrate = std::max(max_bitrate, 1200); | |
| return max_bitrate; | |
| } | |
| int GetDefaultMaxQp(VideoCodecType codec_type) { | |
| switch (codec_type) { | |
| case kVideoCodecH264: | |
| case kVideoCodecH265: | |
| return kDefaultVideoMaxQpH26x; | |
| case kVideoCodecVP8: | |
| case kVideoCodecVP9: | |
| case kVideoCodecGeneric: | |
| return kDefaultVideoMaxQpVpx; | |
| case kVideoCodecAV1: | |
| return kDefaultVideoMaxQpAv1; | |
| } | |
| } | |
| Resolution NormalizeResolution(const Resolution& resolution, | |
| const VideoEncoderConfig& encoder_config, | |
| int max_num_layers, | |
| const FieldTrialsView& trials) { | |
| if (resolution.width < kMinLayerSize || resolution.height < kMinLayerSize) { | |
| // Resolution is already too low. Avoid further adjustments. | |
| return resolution; | |
| } | |
| std::optional<int> log2_scale_factor = | |
| NormalizeSimulcastSizeExperiment::GetBase2Exponent(trials); | |
| if (log2_scale_factor && (resolution.width < (1 << *log2_scale_factor) || | |
| resolution.height < (1 << *log2_scale_factor))) { | |
| // The experimental scale factor is too large for the given input | |
| // resolution. Fall back to the default logic. | |
| log2_scale_factor = std::nullopt; | |
| } | |
| if (!log2_scale_factor) { | |
| // If `scale_resolution_down_by` is not set, default to 1/2 scaling. If | |
| // `scale_resolution_down_by` is set for one or more simulcast layers, | |
| // normalize the resolution to be a multiple of the maximum scale factor if | |
| // both of the following conditions are true: | |
| // 1. All configured scale factors are power of two. | |
| // 2. The maximum scale factor is greater than one. | |
| // Otherwise, keep the resolution unchanged. | |
| if (!encoder_config.HasScaleResolutionDownBy()) { | |
| log2_scale_factor = max_num_layers - 1; | |
| } else if (IsScaleFactorsPowerOfTwo(encoder_config)) { | |
| auto smallest_layer = absl::c_max_element( | |
| encoder_config.simulcast_layers, [](const auto& a, const auto& b) { | |
| return a.scale_resolution_down_by < b.scale_resolution_down_by; | |
| }); | |
| double max_scale_factor = smallest_layer->scale_resolution_down_by; | |
| if (max_scale_factor > 1.0) { | |
| log2_scale_factor = static_cast<int>(std::log2(max_scale_factor)); | |
| } | |
| } | |
| } | |
| if (!log2_scale_factor || (resolution.width < (1 << *log2_scale_factor) || | |
| resolution.height < (1 << *log2_scale_factor))) { | |
| // Too large scale factor. | |
| return resolution; | |
| } | |
| return {.width = (resolution.width >> *log2_scale_factor) | |
| << *log2_scale_factor, | |
| .height = (resolution.height >> *log2_scale_factor) | |
| << *log2_scale_factor}; | |
| } | |
| // Override bitrate limits and other stream settings with values from | |
| // `encoder_config.simulcast_layers` which come from `RtpEncodingParameters`. | |
| void OverrideStreamSettings( | |
| const VideoEncoderConfig& encoder_config, | |
| const std::optional<DataRate>& experimental_min_bitrate, | |
| std::vector<VideoStream>& layers) { | |
| RTC_DCHECK_LE(layers.size(), encoder_config.simulcast_layers.size()); | |
| // Allow an experiment to override the minimum bitrate for the lowest | |
| // spatial layer. The experiment's configuration has the lowest priority. | |
| layers[0].min_bitrate_bps = | |
| experimental_min_bitrate | |
| .value_or(DataRate::BitsPerSec(kDefaultMinVideoBitrateBps)) | |
| .bps<int>(); | |
| const bool temporal_layers_supported = | |
| IsTemporalLayersSupported(encoder_config.codec_type); | |
| for (size_t i = 0; i < layers.size(); ++i) { | |
| const VideoStream& overrides = encoder_config.simulcast_layers[i]; | |
| VideoStream& layer = layers[i]; | |
| layer.active = overrides.active; | |
| layer.scalability_mode = overrides.scalability_mode; | |
| layer.scale_resolution_down_to = overrides.scale_resolution_down_to; | |
| layer.scale_resolution_down_by = overrides.scale_resolution_down_by; | |
| // Update with configured num temporal layers if supported by codec. | |
| if (overrides.num_temporal_layers > 0 && temporal_layers_supported) { | |
| layer.num_temporal_layers = *overrides.num_temporal_layers; | |
| } | |
| if (overrides.max_framerate > 0) { | |
| layer.max_framerate = overrides.max_framerate; | |
| } | |
| // Update simulcast bitrates with configured min and max bitrate. | |
| if (overrides.min_bitrate_bps > 0) { | |
| layer.min_bitrate_bps = overrides.min_bitrate_bps; | |
| } | |
| if (overrides.max_bitrate_bps > 0) { | |
| layer.max_bitrate_bps = overrides.max_bitrate_bps; | |
| } | |
| if (overrides.target_bitrate_bps > 0) { | |
| layer.target_bitrate_bps = overrides.target_bitrate_bps; | |
| } | |
| if (overrides.min_bitrate_bps > 0 && overrides.max_bitrate_bps > 0) { | |
| // Min and max bitrate are configured. | |
| // Set target to 3/4 of the max bitrate (or to max if below min). | |
| if (overrides.target_bitrate_bps <= 0) | |
| layer.target_bitrate_bps = layer.max_bitrate_bps * 3 / 4; | |
| if (layer.target_bitrate_bps < layer.min_bitrate_bps) | |
| layer.target_bitrate_bps = layer.max_bitrate_bps; | |
| } else if (overrides.min_bitrate_bps > 0) { | |
| // Only min bitrate is configured, make sure target/max are above min. | |
| layer.target_bitrate_bps = | |
| std::max(layer.target_bitrate_bps, layer.min_bitrate_bps); | |
| layer.max_bitrate_bps = | |
| std::max(layer.max_bitrate_bps, layer.min_bitrate_bps); | |
| } else if (overrides.max_bitrate_bps > 0) { | |
| // Only max bitrate is configured, make sure min/target are below max. | |
| // Keep target bitrate if it is set explicitly in encoding config. | |
| // Otherwise set target bitrate to 3/4 of the max bitrate | |
| // or the one calculated from GetSimulcastConfig() which is larger. | |
| layer.min_bitrate_bps = | |
| std::min(layer.min_bitrate_bps, layer.max_bitrate_bps); | |
| if (overrides.target_bitrate_bps <= 0) { | |
| layer.target_bitrate_bps = | |
| std::max(layer.target_bitrate_bps, layer.max_bitrate_bps * 3 / 4); | |
| } | |
| layer.target_bitrate_bps = | |
| std::clamp(layer.target_bitrate_bps, layer.min_bitrate_bps, | |
| layer.max_bitrate_bps); | |
| } | |
| if (overrides.max_qp > 0) { | |
| layer.max_qp = overrides.max_qp; | |
| } else if (encoder_config.max_qp > 0) { | |
| layer.max_qp = encoder_config.max_qp; | |
| } else { | |
| layer.max_qp = GetDefaultMaxQp(encoder_config.codec_type); | |
| } | |
| } | |
| bool is_highest_layer_max_bitrate_configured = | |
| encoder_config.simulcast_layers[layers.size() - 1].max_bitrate_bps > 0; | |
| bool is_screencast = | |
| encoder_config.content_type == VideoEncoderConfig::ContentType::kScreen; | |
| if (!is_screencast && !is_highest_layer_max_bitrate_configured && | |
| encoder_config.max_bitrate_bps > 0) { | |
| // No application-configured maximum for the largest layer. | |
| // If there is bitrate leftover, give it to the largest layer. | |
| BoostMaxSimulcastLayer(DataRate::BitsPerSec(encoder_config.max_bitrate_bps), | |
| &layers); | |
| } | |
| // Sort the layers by max_bitrate_bps, they might not always be from | |
| // smallest to biggest | |
| std::vector<size_t> index(layers.size()); | |
| std::iota(index.begin(), index.end(), 0); | |
| absl::c_stable_sort(index, [&layers](size_t a, size_t b) { | |
| return layers[a].max_bitrate_bps < layers[b].max_bitrate_bps; | |
| }); | |
| if (!layers[index[0]].active) { | |
| // Adjust min bitrate of the first active layer to allow it to go as low as | |
| // the lowest (now inactive) layer could. | |
| // Otherwise, if e.g. a single HD stream is active, it would have 600kbps | |
| // min bitrate, which would always be allocated to the stream. | |
| // This would lead to congested network, dropped frames and overall bad | |
| // experience. | |
| const int min_configured_bitrate = layers[index[0]].min_bitrate_bps; | |
| for (size_t i = 0; i < layers.size(); ++i) { | |
| if (layers[index[i]].active) { | |
| layers[index[i]].min_bitrate_bps = min_configured_bitrate; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| } // namespace | |
| EncoderStreamFactory::EncoderStreamFactory( | |
| const VideoEncoder::EncoderInfo& encoder_info, | |
| std::optional<VideoSourceRestrictions> restrictions) | |
| : encoder_info_requested_resolution_alignment_( | |
| encoder_info.requested_resolution_alignment), | |
| restrictions_(restrictions) {} | |
| std::vector<VideoStream> EncoderStreamFactory::CreateEncoderStreams( | |
| const FieldTrialsView& trials, | |
| int frame_width, | |
| int frame_height, | |
| const VideoEncoderConfig& encoder_config) { | |
| RTC_DCHECK_GT(encoder_config.number_of_streams, 0); | |
| RTC_DCHECK_GE(encoder_config.simulcast_layers.size(), | |
| encoder_config.number_of_streams); | |
| const std::optional<DataRate> experimental_min_bitrate = | |
| GetExperimentalMinVideoBitrate(trials, encoder_config.codec_type); | |
| bool is_simulcast = (encoder_config.number_of_streams > 1); | |
| // If scalability mode was specified, don't treat {active,inactive,inactive} | |
| // as simulcast since the simulcast configuration assumes very low bitrates | |
| // on the first layer. This would prevent rampup of multiple spatial layers. | |
| // See https://crbug.com/webrtc/15041. | |
| if (is_simulcast && | |
| encoder_config.simulcast_layers[0].scalability_mode.has_value()) { | |
| // Require at least one non-first layer to be active for is_simulcast=true. | |
| is_simulcast = false; | |
| for (size_t i = 1; i < encoder_config.simulcast_layers.size(); ++i) { | |
| if (encoder_config.simulcast_layers[i].active) { | |
| is_simulcast = true; | |
| break; | |
| } | |
| } | |
| } | |
| std::vector<VideoStream> streams; | |
| if (is_simulcast || | |
| SimulcastUtility::IsConferenceModeScreenshare(encoder_config)) { | |
| streams = CreateSimulcastOrConferenceModeScreenshareStreams( | |
| trials, frame_width, frame_height, encoder_config, | |
| experimental_min_bitrate); | |
| } else { | |
| streams = CreateDefaultVideoStreams( | |
| frame_width, frame_height, encoder_config, experimental_min_bitrate); | |
| } | |
| // The bitrate priority currently implemented on a per-sender level, so we | |
| // just set it for the first simulcast layer. | |
| RTC_DCHECK_GT(streams.size(), 0); | |
| streams[0].bitrate_priority = encoder_config.bitrate_priority; | |
| return streams; | |
| } | |
| std::vector<VideoStream> EncoderStreamFactory::CreateDefaultVideoStreams( | |
| int width, | |
| int height, | |
| const VideoEncoderConfig& encoder_config, | |
| const std::optional<DataRate>& experimental_min_bitrate) const { | |
| bool is_screencast = | |
| encoder_config.content_type == VideoEncoderConfig::ContentType::kScreen; | |
| // The max bitrate specified by the API. | |
| // - `encoder_config.simulcast_layers[0].max_bitrate_bps` comes from the first | |
| // RtpEncodingParamters, which is the encoding of this stream. | |
| // - `encoder_config.max_bitrate_bps` comes from SDP; "b=AS" or conditionally | |
| // "x-google-max-bitrate". | |
| // If `api_max_bitrate_bps` has a value then it is positive. | |
| std::optional<int> api_max_bitrate_bps; | |
| if (encoder_config.simulcast_layers[0].max_bitrate_bps > 0) { | |
| api_max_bitrate_bps = encoder_config.simulcast_layers[0].max_bitrate_bps; | |
| } | |
| if (encoder_config.max_bitrate_bps > 0) { | |
| api_max_bitrate_bps = | |
| api_max_bitrate_bps.has_value() | |
| ? std::min(encoder_config.max_bitrate_bps, *api_max_bitrate_bps) | |
| : encoder_config.max_bitrate_bps; | |
| } | |
| // For unset max bitrates set default bitrate for non-simulcast. | |
| int max_bitrate_bps = | |
| api_max_bitrate_bps.has_value() | |
| ? *api_max_bitrate_bps | |
| : GetMaxDefaultVideoBitrateKbps(width, height, is_screencast) * 1000; | |
| int min_bitrate_bps = | |
| experimental_min_bitrate | |
| ? saturated_cast<int>(experimental_min_bitrate->bps()) | |
| : kDefaultMinVideoBitrateBps; | |
| if (encoder_config.simulcast_layers[0].min_bitrate_bps > 0) { | |
| // Use set min bitrate. | |
| min_bitrate_bps = encoder_config.simulcast_layers[0].min_bitrate_bps; | |
| // If only min bitrate is configured, make sure max is above min. | |
| if (!api_max_bitrate_bps.has_value()) | |
| max_bitrate_bps = std::max(min_bitrate_bps, max_bitrate_bps); | |
| } | |
| int max_framerate = (encoder_config.simulcast_layers[0].max_framerate > 0) | |
| ? encoder_config.simulcast_layers[0].max_framerate | |
| : kDefaultVideoMaxFramerate; | |
| VideoStream layer; | |
| layer.max_framerate = max_framerate; | |
| layer.scale_resolution_down_by = | |
| encoder_config.simulcast_layers[0].scale_resolution_down_by; | |
| layer.scale_resolution_down_to = | |
| encoder_config.simulcast_layers[0].scale_resolution_down_to; | |
| // Note: VP9 seems to have be sending if any layer is active, | |
| // (see `UpdateSendState`) and still use parameters only from | |
| // encoder_config.simulcast_layers[0]. | |
| layer.active = absl::c_any_of(encoder_config.simulcast_layers, | |
| [](const auto& layer) { return layer.active; }); | |
| if (encoder_config.simulcast_layers[0].scale_resolution_down_to) { | |
| auto res = GetLayerResolutionFromScaleResolutionDownTo( | |
| width, height, | |
| *encoder_config.simulcast_layers[0].scale_resolution_down_to); | |
| layer.width = res.width; | |
| layer.height = res.height; | |
| } else if (encoder_config.simulcast_layers[0].scale_resolution_down_by > 1.) { | |
| Resolution res = ScaleResolutionDownBy( | |
| {.width = width, .height = height}, | |
| encoder_config.simulcast_layers[0].scale_resolution_down_by); | |
| layer.width = res.width; | |
| layer.height = res.height; | |
| } else { | |
| layer.width = width; | |
| layer.height = height; | |
| } | |
| if (encoder_config.codec_type == VideoCodecType::kVideoCodecVP9) { | |
| RTC_DCHECK(encoder_config.encoder_specific_settings); | |
| // Use VP9 SVC layering from codec settings which might be initialized | |
| // though field trial in ConfigureVideoEncoderSettings. | |
| VideoCodecVP9 vp9_settings; | |
| encoder_config.encoder_specific_settings->FillVideoCodecVp9(&vp9_settings); | |
| layer.num_temporal_layers = vp9_settings.numberOfTemporalLayers; | |
| // Number of spatial layers is signalled differently from different call | |
| // sites (sigh), pick the max as we are interested in the upper bound. | |
| int num_spatial_layers = | |
| std::max({encoder_config.simulcast_layers.size(), | |
| encoder_config.spatial_layers.size(), | |
| size_t{vp9_settings.numberOfSpatialLayers}}); | |
| if (width * height > 0 && | |
| (layer.num_temporal_layers > 1u || num_spatial_layers > 1)) { | |
| // In SVC mode, the VP9 max bitrate is determined by SvcConfig, instead of | |
| // GetMaxDefaultVideoBitrateKbps(). | |
| std::vector<SpatialLayer> svc_layers = | |
| GetSvcConfig(width, height, max_framerate, | |
| /*first_active_layer=*/0, num_spatial_layers, | |
| *layer.num_temporal_layers, is_screencast); | |
| int sum_max_bitrates_kbps = 0; | |
| for (const SpatialLayer& spatial_layer : svc_layers) { | |
| sum_max_bitrates_kbps += spatial_layer.maxBitrate; | |
| } | |
| RTC_DCHECK_GE(sum_max_bitrates_kbps, 0); | |
| if (!api_max_bitrate_bps.has_value()) { | |
| max_bitrate_bps = sum_max_bitrates_kbps * 1000; | |
| } else if (encoder_config.simulcast_layers[0].max_bitrate_bps <= 0) { | |
| // Encoding max bitrate is kept if configured. | |
| max_bitrate_bps = | |
| std::min(max_bitrate_bps, sum_max_bitrates_kbps * 1000); | |
| } | |
| max_bitrate_bps = std::max(min_bitrate_bps, max_bitrate_bps); | |
| } | |
| } | |
| // In the case that the application sets a max bitrate that's lower than the | |
| // min bitrate, we adjust it down (see bugs.webrtc.org/9141). | |
| layer.min_bitrate_bps = std::min(min_bitrate_bps, max_bitrate_bps); | |
| if (encoder_config.simulcast_layers[0].target_bitrate_bps <= 0) { | |
| layer.target_bitrate_bps = max_bitrate_bps; | |
| } else { | |
| layer.target_bitrate_bps = std::min( | |
| encoder_config.simulcast_layers[0].target_bitrate_bps, max_bitrate_bps); | |
| } | |
| layer.max_bitrate_bps = max_bitrate_bps; | |
| layer.bitrate_priority = encoder_config.bitrate_priority; | |
| if (encoder_config.max_qp > 0) { | |
| layer.max_qp = encoder_config.max_qp; | |
| } else { | |
| layer.max_qp = GetDefaultMaxQp(encoder_config.codec_type); | |
| } | |
| if (IsTemporalLayersSupported(encoder_config.codec_type)) { | |
| // Use configured number of temporal layers if set. | |
| if (encoder_config.simulcast_layers[0].num_temporal_layers) { | |
| layer.num_temporal_layers = | |
| *encoder_config.simulcast_layers[0].num_temporal_layers; | |
| } | |
| } | |
| layer.scalability_mode = encoder_config.simulcast_layers[0].scalability_mode; | |
| return {layer}; | |
| } | |
| std::vector<VideoStream> | |
| EncoderStreamFactory::CreateSimulcastOrConferenceModeScreenshareStreams( | |
| const FieldTrialsView& trials, | |
| int width, | |
| int height, | |
| const VideoEncoderConfig& encoder_config, | |
| const std::optional<DataRate>& experimental_min_bitrate) const { | |
| std::vector<Resolution> resolutions = | |
| GetStreamResolutions(trials, width, height, encoder_config); | |
| // Use legacy simulcast screenshare if conference mode is explicitly enabled | |
| // or use the regular simulcast configuration path which is generic. | |
| std::vector<VideoStream> layers = GetSimulcastConfig( | |
| resolutions, | |
| SimulcastUtility::IsConferenceModeScreenshare(encoder_config), | |
| IsTemporalLayersSupported(encoder_config.codec_type), trials, | |
| encoder_config.codec_type); | |
| OverrideStreamSettings(encoder_config, experimental_min_bitrate, layers); | |
| return layers; | |
| } | |
| Resolution EncoderStreamFactory::GetLayerResolutionFromScaleResolutionDownTo( | |
| int frame_width, | |
| int frame_height, | |
| Resolution scale_resolution_down_to) const { | |
| // Make frame and `scale_resolution_down_to` have matching orientation. | |
| if ((frame_width < frame_height) != | |
| (scale_resolution_down_to.width < scale_resolution_down_to.height)) { | |
| scale_resolution_down_to = {.width = scale_resolution_down_to.height, | |
| .height = scale_resolution_down_to.width}; | |
| } | |
| // Downscale by smallest scaling factor, if necessary. | |
| if (frame_width > 0 && frame_height > 0 && | |
| (scale_resolution_down_to.width < frame_width || | |
| scale_resolution_down_to.height < frame_height)) { | |
| double scale_factor = std::min( | |
| scale_resolution_down_to.width / static_cast<double>(frame_width), | |
| scale_resolution_down_to.height / static_cast<double>(frame_height)); | |
| frame_width = std::round(frame_width * scale_factor); | |
| frame_height = std::round(frame_height * scale_factor); | |
| } | |
| Resolution frame = {.width = frame_width, .height = frame_height}; | |
| // Maybe adapt further based on restrictions and encoder alignment. | |
| VideoAdapter adapter(encoder_info_requested_resolution_alignment_); | |
| adapter.OnOutputFormatRequest(frame.ToPair(), frame.PixelCount(), | |
| std::nullopt); | |
| if (restrictions_) { | |
| VideoSinkWants wants; | |
| wants.is_active = true; | |
| wants.target_pixel_count = restrictions_->target_pixels_per_frame(); | |
| wants.max_pixel_count = | |
| dchecked_cast<int>(restrictions_->max_pixels_per_frame().value_or( | |
| std::numeric_limits<int>::max())); | |
| wants.aggregates.emplace(VideoSinkWants::Aggregates()); | |
| wants.resolution_alignment = encoder_info_requested_resolution_alignment_; | |
| adapter.OnSinkWants(wants); | |
| } | |
| int cropped_width, cropped_height; | |
| int out_width = 0, out_height = 0; | |
| if (!adapter.AdaptFrameResolution(frame_width, frame_height, 0, | |
| &cropped_width, &cropped_height, &out_width, | |
| &out_height)) { | |
| RTC_LOG(LS_ERROR) << "AdaptFrameResolution returned false!"; | |
| } | |
| return {.width = out_width, .height = out_height}; | |
| } | |
| std::vector<Resolution> EncoderStreamFactory::GetStreamResolutions( | |
| const FieldTrialsView& trials, | |
| int width, | |
| int height, | |
| const VideoEncoderConfig& encoder_config) const { | |
| std::vector<Resolution> resolutions; | |
| if (SimulcastUtility::IsConferenceModeScreenshare(encoder_config)) { | |
| for (size_t i = 0; i < encoder_config.number_of_streams; ++i) { | |
| resolutions.push_back({.width = width, .height = height}); | |
| } | |
| } else { | |
| const bool has_scale_resolution_down_to = | |
| encoder_config.HasScaleResolutionDownTo(); | |
| size_t min_num_layers = FindRequiredActiveLayers(encoder_config); | |
| size_t max_num_layers = | |
| !has_scale_resolution_down_to | |
| ? LimitSimulcastLayerCount( | |
| min_num_layers, encoder_config.number_of_streams, width, | |
| height, trials, encoder_config.codec_type) | |
| : encoder_config.number_of_streams; | |
| RTC_DCHECK_LE(max_num_layers, encoder_config.number_of_streams); | |
| // When the `scale_resolution_down_to` API is used, disable upper layers | |
| // that are bigger than what adaptation restrictions allow. For example if | |
| // restrictions are 540p, simulcast 180p:360p:720p becomes 180p:360p:- as | |
| // opposed to 180p:360p:540p. This makes CPU adaptation consistent with BW | |
| // adaptation (bitrate allocator disabling layers rather than downscaling) | |
| // and means we don't have to break power of two optimization paths (i.e. | |
| // S-modes based simulcast). Note that the lowest layer is never disabled. | |
| if (has_scale_resolution_down_to && restrictions_.has_value() && | |
| restrictions_->max_pixels_per_frame().has_value()) { | |
| int max_pixels = | |
| dchecked_cast<int>(restrictions_->max_pixels_per_frame().value()); | |
| int prev_pixel_count = | |
| encoder_config.simulcast_layers[0] | |
| .scale_resolution_down_to.value_or(Resolution()) | |
| .PixelCount(); | |
| std::optional<size_t> restricted_num_layers; | |
| for (size_t i = 1; i < max_num_layers; ++i) { | |
| int pixel_count = encoder_config.simulcast_layers[i] | |
| .scale_resolution_down_to.value_or(Resolution()) | |
| .PixelCount(); | |
| if (!restricted_num_layers.has_value() && max_pixels < pixel_count) { | |
| // Current layer is the highest layer allowed by restrictions. | |
| restricted_num_layers = i; | |
| } | |
| if (pixel_count < prev_pixel_count) { | |
| // Cannot limit layers because config is not lower-to-higher. | |
| restricted_num_layers = std::nullopt; | |
| break; | |
| } | |
| prev_pixel_count = pixel_count; | |
| } | |
| max_num_layers = restricted_num_layers.value_or(max_num_layers); | |
| } | |
| Resolution norm_resolution = | |
| NormalizeResolution({.width = width, .height = height}, encoder_config, | |
| max_num_layers, trials); | |
| const bool has_scale_resolution_down_by = | |
| encoder_config.HasScaleResolutionDownBy(); | |
| resolutions.resize(max_num_layers); | |
| for (size_t i = 0; i < max_num_layers; i++) { | |
| if (encoder_config.simulcast_layers[i] | |
| .scale_resolution_down_to.has_value()) { | |
| resolutions[i] = GetLayerResolutionFromScaleResolutionDownTo( | |
| norm_resolution.width, norm_resolution.height, | |
| *encoder_config.simulcast_layers[i].scale_resolution_down_to); | |
| } else if (has_scale_resolution_down_by) { | |
| const double scale_resolution_down_by = std::max( | |
| encoder_config.simulcast_layers[i].scale_resolution_down_by, 1.0); | |
| resolutions[i] = | |
| ScaleResolutionDownBy(norm_resolution, scale_resolution_down_by); | |
| } else { | |
| // Resolutions with default 1/2 scale factor, from low to high. | |
| resolutions[i].width = | |
| norm_resolution.width >> (max_num_layers - i - 1); | |
| resolutions[i].height = | |
| norm_resolution.height >> (max_num_layers - i - 1); | |
| } | |
| } | |
| } | |
| return resolutions; | |
| } | |
| } // namespace webrtc |