tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

VideoStreamFactory.cpp (13661B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
      6 
      7 #include "VideoStreamFactory.h"
      8 
      9 #include <stdint.h>
     10 
     11 #include <algorithm>
     12 #include <cmath>
     13 #include <limits>
     14 #include <vector>
     15 
     16 #include "GMPUtils.h"
     17 #include "VideoConduit.h"
     18 #include "common/browser_logging/CSFLog.h"
     19 #include "mozilla/Assertions.h"
     20 #include "mozilla/gfx/Point.h"
     21 #include "video/config/video_encoder_config.h"
     22 
     23 template <class t>
     24 void ConstrainPreservingAspectRatio(uint16_t aMaxWidth, uint16_t aMaxHeight,
     25                                    t* aWidth, t* aHeight) {
     26  if (((*aWidth) <= aMaxWidth) && ((*aHeight) <= aMaxHeight)) {
     27    return;
     28  }
     29 
     30  if ((*aWidth) * aMaxHeight > aMaxWidth * (*aHeight)) {
     31    (*aHeight) = aMaxWidth * (*aHeight) / (*aWidth);
     32    (*aWidth) = aMaxWidth;
     33  } else {
     34    (*aWidth) = aMaxHeight * (*aWidth) / (*aHeight);
     35    (*aHeight) = aMaxHeight;
     36  }
     37 }
     38 
     39 namespace mozilla {
     40 
     41 #ifdef LOGTAG
     42 #  undef LOGTAG
     43 #endif
     44 #define LOGTAG "WebrtcVideoSessionConduit"
     45 
     46 #define DEFAULT_VIDEO_MAX_FRAMERATE 30u
     47 
     48 #define MB_OF(w, h) \
     49  ((unsigned int)(((((w) + 15) >> 4)) * ((unsigned int)(((h) + 15) >> 4))))
     50 // For now, try to set the max rates well above the knee in the curve.
     51 // Chosen somewhat arbitrarily; it's hard to find good data oriented for
     52 // realtime interactive/talking-head recording.  These rates assume
     53 // 30fps.
     54 
     55 // XXX Populate this based on a pref (which we should consider sorting because
     56 // people won't assume they need to).
     57 static constexpr VideoStreamFactory::ResolutionAndBitrateLimits
     58    kResolutionAndBitrateLimits[] = {
     59        // clang-format off
     60  {MB_OF(1920, 1200), KBPS(1500), KBPS(2000), KBPS(10000)}, // >HD (3K, 4K, etc)
     61  {MB_OF(1280, 720), KBPS(1200), KBPS(1500), KBPS(5000)}, // HD ~1080-1200
     62  {MB_OF(800, 480), KBPS(200), KBPS(800), KBPS(2500)}, // HD ~720
     63  {MB_OF(480, 270), KBPS(150), KBPS(500), KBPS(2000)}, // WVGA
     64  {std::max(MB_OF(400, 240), MB_OF(352, 288)), KBPS(125), KBPS(300), KBPS(1300)}, // VGA
     65  {MB_OF(176, 144), KBPS(100), KBPS(150), KBPS(500)}, // WQVGA, CIF
     66  {0 , KBPS(40), KBPS(80), KBPS(250)} // QCIF and below
     67        // clang-format on
     68 };
     69 
     70 auto VideoStreamFactory::GetLimitsFor(gfx::IntSize aSize, int aCapBps /* = 0 */)
     71    -> ResolutionAndBitrateLimits {
     72  // max bandwidth should be proportional (not linearly!) to resolution, and
     73  // proportional (perhaps linearly, or close) to current frame rate.
     74  int fs = MB_OF(aSize.width, aSize.height);
     75 
     76  for (const auto& resAndLimits : kResolutionAndBitrateLimits) {
     77    if (fs >= resAndLimits.resolution_in_mb &&
     78        // pick the highest range where at least start rate is within cap
     79        // (or if we're at the end of the array).
     80        (aCapBps == 0 || resAndLimits.start_bitrate_bps <= aCapBps ||
     81         resAndLimits.resolution_in_mb == 0)) {
     82      return resAndLimits;
     83    }
     84  }
     85 
     86  MOZ_CRASH("Loop should have handled fallback");
     87 }
     88 
     89 /**
     90 * Function to set the encoding bitrate limits based on incoming frame size and
     91 * rate
     92 * @param size: dimensions of the frame
     93 * @param min: minimum bitrate in bps
     94 * @param start: bitrate in bps that the encoder should start with
     95 * @param cap: user-enforced max bitrate, or 0
     96 * @param pref_cap: cap enforced by prefs
     97 * @param negotiated_cap: cap negotiated through SDP
     98 * @param aVideoStream stream to apply bitrates to
     99 */
    100 static void SelectBitrates(gfx::IntSize size, int min, int start, int cap,
    101                           int pref_cap, int negotiated_cap,
    102                           webrtc::VideoStream& aVideoStream) {
    103  int& out_min = aVideoStream.min_bitrate_bps;
    104  int& out_start = aVideoStream.target_bitrate_bps;
    105  int& out_max = aVideoStream.max_bitrate_bps;
    106 
    107  VideoStreamFactory::ResolutionAndBitrateLimits resAndLimits =
    108      VideoStreamFactory::GetLimitsFor(size);
    109  out_min = MinIgnoreZero(resAndLimits.min_bitrate_bps, cap);
    110  out_start = MinIgnoreZero(resAndLimits.start_bitrate_bps, cap);
    111  out_max = MinIgnoreZero(resAndLimits.max_bitrate_bps, cap);
    112 
    113  // Note: negotiated_cap is the max transport bitrate - it applies to
    114  // a single codec encoding, but should also apply to the sum of all
    115  // simulcast layers in this encoding! So sum(layers.maxBitrate) <=
    116  // negotiated_cap
    117  // Note that out_max already has had pref_cap applied to it
    118  out_max = MinIgnoreZero(negotiated_cap, out_max);
    119  out_min = std::min(out_min, out_max);
    120  out_start = std::min(out_start, out_max);
    121 
    122  if (min && min > out_min) {
    123    out_min = min;
    124  }
    125  // If we try to set a minimum bitrate that is too low, ViE will reject it.
    126  out_min = std::max(kViEMinCodecBitrate_bps, out_min);
    127  out_max = std::max(kViEMinCodecBitrate_bps, out_max);
    128  if (start && start > out_start) {
    129    out_start = start;
    130  }
    131 
    132  // Ensure that min <= start <= max
    133  if (out_min > out_max) {
    134    out_min = out_max;
    135  }
    136  out_start = std::clamp(out_start, out_min, out_max);
    137 
    138  MOZ_ASSERT(pref_cap == 0 || out_max <= pref_cap);
    139 }
    140 
    141 void VideoStreamFactory::SelectResolutionAndMaxFramerate(
    142    gfx::IntSize aSize, const VideoCodecConfig::Encoding& aEncoding,
    143    webrtc::VideoStream& aVideoStream) {
    144  MOZ_ASSERT(aSize.width > 0);
    145  MOZ_ASSERT(aSize.height > 0);
    146  MOZ_ASSERT(aEncoding.constraints.scaleDownBy >= 1.0);
    147  gfx::IntSize newSize(0, 0);
    148 
    149  newSize = CalculateScaledResolution(aSize, aEncoding.constraints.scaleDownBy);
    150 
    151  if (newSize.width == 0 || newSize.height == 0) {
    152    aVideoStream.width = aVideoStream.height = 0;
    153    return;
    154  }
    155 
    156  uint16_t max_width = mCodecConfig.mEncodingConstraints.maxWidth;
    157  uint16_t max_height = mCodecConfig.mEncodingConstraints.maxHeight;
    158  if (max_width || max_height) {
    159    max_width = max_width ? max_width : UINT16_MAX;
    160    max_height = max_height ? max_height : UINT16_MAX;
    161    ConstrainPreservingAspectRatio(max_width, max_height, &newSize.width,
    162                                   &newSize.height);
    163  }
    164 
    165  MOZ_ASSERT(newSize.width > 0);
    166  MOZ_ASSERT(newSize.height > 0);
    167  aVideoStream.width = newSize.width;
    168  aVideoStream.height = newSize.height;
    169  SelectMaxFramerateForAllStreams(newSize);
    170 
    171  CSFLogInfo(LOGTAG, "%s Input frame %ux%u, RID %s scaling to %zux%zu",
    172             __FUNCTION__, aSize.width, aSize.height, aEncoding.rid.c_str(),
    173             aVideoStream.width, aVideoStream.height);
    174 
    175  // mMaxFramerateForAllStreams is based on codec-wide stuff like fmtp, and
    176  // hard-coded limits based on the source resolution.
    177  // mCodecConfig.mEncodingConstraints.maxFps does not take the hard-coded
    178  // limits into account, so we have mMaxFramerateForAllStreams which
    179  // incorporates those. Per-encoding max framerate is based on parameters
    180  // from JS, and maybe rid
    181  unsigned int max_framerate = SelectFrameRate(
    182      mMaxFramerateForAllStreams, {aVideoStream.width, aVideoStream.height});
    183  max_framerate = std::min(
    184      WebrtcVideoConduit::ToLibwebrtcMaxFramerate(aEncoding.constraints.maxFps),
    185      max_framerate);
    186  if (max_framerate >= std::numeric_limits<int>::max()) {
    187    // If nothing has specified any kind of limit (uncommon), pick something
    188    // reasonable.
    189    max_framerate = DEFAULT_VIDEO_MAX_FRAMERATE;
    190  }
    191  aVideoStream.max_framerate = static_cast<int>(max_framerate);
    192 }
    193 
    194 std::vector<webrtc::VideoStream> VideoStreamFactory::CreateEncoderStreams(
    195    const webrtc::FieldTrialsView& field_trials, int aWidth, int aHeight,
    196    const webrtc::VideoEncoderConfig& aConfig) {
    197  mEncodeQueue->AssertOnCurrentThread();
    198  const size_t streamCount = aConfig.number_of_streams;
    199 
    200  MOZ_RELEASE_ASSERT(streamCount >= 1, "Should request at least one stream");
    201  MOZ_RELEASE_ASSERT(streamCount <= aConfig.simulcast_layers.size());
    202 
    203  std::vector<webrtc::VideoStream> streams;
    204  streams.reserve(streamCount);
    205 
    206  for (size_t idx = 0; idx < streamCount; ++idx) {
    207    webrtc::VideoStream video_stream = aConfig.simulcast_layers[idx];
    208    const auto& encoding = mCodecConfig.mEncodings[idx];
    209    MOZ_ASSERT(video_stream.active == encoding.active);
    210 
    211    SelectResolutionAndMaxFramerate({aWidth, aHeight}, encoding, video_stream);
    212 
    213    CSFLogInfo(
    214        LOGTAG,
    215        "%s Stream %zu with RID %s scaling %dx%d->%zux%zu; scaleDownBy=%.2f).",
    216        __FUNCTION__, idx, encoding.rid.c_str(), aWidth, aHeight,
    217        video_stream.width, video_stream.height,
    218        encoding.constraints.scaleDownBy);
    219 
    220    if (video_stream.width == 0 || video_stream.height == 0) {
    221      CSFLogInfo(LOGTAG, "%s Stream with RID %s ignored: has no resolution.",
    222                 __FUNCTION__, encoding.rid.c_str());
    223    }
    224 
    225    SelectBitrates({video_stream.width, video_stream.height}, mMinBitrate,
    226                   mStartBitrate,
    227                   SaturatingCast<int>(encoding.constraints.maxBr),
    228                   mPrefMaxBitrate, mNegotiatedMaxBitrate, video_stream);
    229 
    230    CSFLogInfo(LOGTAG,
    231               "%s Stream with RID %s maxFps=%d (global max fps = %u), "
    232               "bitrate=[%dkbps, %dkbps, %dkbps]",
    233               __FUNCTION__, encoding.rid.c_str(), video_stream.max_framerate,
    234               (unsigned)mMaxFramerateForAllStreams,
    235               video_stream.min_bitrate_bps / 1000,
    236               video_stream.target_bitrate_bps / 1000,
    237               video_stream.max_bitrate_bps / 1000);
    238 
    239    video_stream.bitrate_priority = aConfig.bitrate_priority;
    240    video_stream.max_qp = kQpMax;
    241 
    242    if (streamCount > 1) {
    243      video_stream.num_temporal_layers = 2;
    244      if (mCodecConfig.mName == "H264") {
    245 #ifdef ANDROID
    246        video_stream.num_temporal_layers = 1;
    247 #else
    248        if (!HaveGMPFor("encode-video"_ns, {"moz-h264-temporal-svc"_ns})) {
    249          video_stream.num_temporal_layers = 1;
    250        }
    251 #endif
    252      }
    253      // XXX Bug 1390215 investigate using more of
    254      // simulcast.cc:GetSimulcastConfig() or our own algorithm to replace it
    255    }
    256 
    257    streams.push_back(video_stream);
    258  }
    259 
    260  MOZ_RELEASE_ASSERT(streams.size(), "Should configure at least one stream");
    261  return streams;
    262 }
    263 
    264 void VideoStreamFactory::SetEncoderInfo(
    265    const webrtc::VideoEncoder::EncoderInfo& aInfo) {
    266  if (!mEncodeQueue) {
    267    mEncodeQueue = Nothing();
    268    mEncodeQueue.emplace(GetCurrentSerialEventTarget());
    269  }
    270  mEncodeQueue->AssertOnCurrentThread();
    271  mRequestedResolutionAlignment =
    272      Some(SaturatingCast<int>(aInfo.requested_resolution_alignment));
    273 }
    274 
    275 gfx::IntSize VideoStreamFactory::CalculateScaledResolution(
    276    gfx::IntSize aSize, double aScaleDownByResolution) {
    277  mEncodeQueue->AssertOnCurrentThread();
    278  // If any adjustments like scaleResolutionDownBy or maxFS are being given
    279  // we want to choose a height and width here to provide for more variety
    280  // in possible resolutions.
    281  int width = aSize.width;
    282  int height = aSize.height;
    283 
    284  if (aScaleDownByResolution > 1) {
    285    width = static_cast<int>(aSize.width / aScaleDownByResolution);
    286    height = static_cast<int>(aSize.height / aScaleDownByResolution);
    287  }
    288 
    289  // Check if we still need to adjust resolution down more due to other
    290  // constraints.
    291  if (mCodecConfig.mEncodingConstraints.maxFs > 0) {
    292    auto currentFs = static_cast<unsigned int>(width * height);
    293    auto maxFs = mCodecConfig.mEncodingConstraints.maxFs * 16 * 16;
    294 
    295    // If our currentFs is greater than maxFs we calculate a width and height
    296    // that will get as close as possible to maxFs and try to maintain aspect
    297    // ratio.
    298    if (currentFs > maxFs) {
    299      if (aSize.width > aSize.height) {  // Landscape
    300        auto aspectRatio = static_cast<double>(aSize.width) / aSize.height;
    301 
    302        height = static_cast<int>(std::sqrt(maxFs / aspectRatio));
    303        width = static_cast<int>(height * aspectRatio);
    304      } else {  // Portrait
    305        auto aspectRatio = static_cast<double>(aSize.height) / aSize.width;
    306 
    307        width = static_cast<int>(std::sqrt(maxFs / aspectRatio));
    308        height = static_cast<int>(width * aspectRatio);
    309      }
    310    }
    311  }
    312 
    313  // Simplest possible adaptation to resolution alignment.
    314  width -= width % *mRequestedResolutionAlignment;
    315  height -= height % *mRequestedResolutionAlignment;
    316 
    317  // Guard against a negative size.
    318  const int minSize = 0;
    319  if (width < minSize || height < minSize) {
    320    width = minSize;
    321    height = minSize;
    322  }
    323 
    324  return gfx::IntSize(width, height);
    325 }
    326 
    327 void VideoStreamFactory::SelectMaxFramerateForAllStreams(gfx::IntSize aSize) {
    328  unsigned int framerate_all_streams =
    329      SelectFrameRate(mMaxFramerateForAllStreams, aSize);
    330  unsigned int maxFrameRate = mMaxFramerateForAllStreams;
    331  if (mMaxFramerateForAllStreams != framerate_all_streams) {
    332    CSFLogDebug(LOGTAG, "%s: framerate changing to %u (from %u)", __FUNCTION__,
    333                framerate_all_streams, maxFrameRate);
    334    mMaxFramerateForAllStreams = framerate_all_streams;
    335  }
    336 }
    337 
    338 unsigned int VideoStreamFactory::SelectFrameRate(unsigned int aOldFramerate,
    339                                                 gfx::IntSize aSize) {
    340  unsigned int new_framerate = aOldFramerate;
    341 
    342  // Limit frame rate based on max-mbps
    343  if (mCodecConfig.mEncodingConstraints.maxMbps) {
    344    unsigned int cur_fs, mb_width, mb_height;
    345 
    346    mb_width = (aSize.width + 15) >> 4;
    347    mb_height = (aSize.height + 15) >> 4;
    348 
    349    cur_fs = mb_width * mb_height;
    350    if (cur_fs > 0) {  // in case no frames have been sent
    351      new_framerate = mCodecConfig.mEncodingConstraints.maxMbps / cur_fs;
    352    }
    353  }
    354 
    355  new_framerate =
    356      std::min(new_framerate, WebrtcVideoConduit::ToLibwebrtcMaxFramerate(
    357                                  mCodecConfig.mEncodingConstraints.maxFps));
    358  return new_framerate;
    359 }
    360 
    361 }  // namespace mozilla