tor-browser

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

VP8TrackEncoder.cpp (25154B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      4 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include "VP8TrackEncoder.h"
      7 
      8 #include <vpx/vp8cx.h>
      9 #include <vpx/vpx_encoder.h>
     10 
     11 #include "DriftCompensation.h"
     12 #include "ImageConversion.h"
     13 #include "VideoSegment.h"
     14 #include "VideoUtils.h"
     15 #include "WebMWriter.h"
     16 #include "mozilla/ProfilerLabels.h"
     17 #include "mozilla/dom/ImageBitmapBinding.h"
     18 #include "mozilla/dom/ImageUtils.h"
     19 #include "mozilla/gfx/2D.h"
     20 #include "mozilla/media/MediaUtils.h"
     21 #include "prsystem.h"
     22 
     23 namespace mozilla {
     24 
     25 LazyLogModule gVP8TrackEncoderLog("VP8TrackEncoder");
     26 #define VP8LOG(level, msg, ...) \
     27  MOZ_LOG(gVP8TrackEncoderLog, level, (msg, ##__VA_ARGS__))
     28 
     29 constexpr int DEFAULT_BITRATE_BPS = 2500000;
     30 constexpr int DEFAULT_KEYFRAME_INTERVAL_MS = 10000;
     31 constexpr int DYNAMIC_MAXKFDIST_CHECK_INTERVAL = 5;
     32 constexpr float DYNAMIC_MAXKFDIST_DIFFACTOR = 0.4;
     33 constexpr float DYNAMIC_MAXKFDIST_KFINTERVAL_FACTOR = 0.75;
     34 constexpr int I420_STRIDE_ALIGN = 16;
     35 
     36 using namespace mozilla::gfx;
     37 using namespace mozilla::layers;
     38 using namespace mozilla::media;
     39 using namespace mozilla::dom;
     40 
     41 namespace {
     42 
     43 template <int N>
     44 static int Aligned(int aValue) {
     45  if (aValue < N) {
     46    return N;
     47  }
     48 
     49  // The `- 1` avoids overreaching when `aValue % N == 0`.
     50  return (((aValue - 1) / N) + 1) * N;
     51 }
     52 
     53 template <int Alignment>
     54 size_t I420Size(int aWidth, int aHeight) {
     55  int yStride = Aligned<Alignment>(aWidth);
     56  int yHeight = aHeight;
     57  size_t yPlaneSize = yStride * yHeight;
     58 
     59  int uvStride = Aligned<Alignment>((aWidth + 1) / 2);
     60  int uvHeight = (aHeight + 1) / 2;
     61  size_t uvPlaneSize = uvStride * uvHeight;
     62 
     63  return yPlaneSize + uvPlaneSize * 2;
     64 }
     65 
     66 nsresult CreateEncoderConfig(int32_t aWidth, int32_t aHeight,
     67                             uint32_t aVideoBitrate, TrackRate aTrackRate,
     68                             int32_t aMaxKeyFrameDistance,
     69                             vpx_codec_enc_cfg_t* config) {
     70  // Encoder configuration structure.
     71  memset(config, 0, sizeof(vpx_codec_enc_cfg_t));
     72  if (vpx_codec_enc_config_default(vpx_codec_vp8_cx(), config, 0)) {
     73    VP8LOG(LogLevel::Error, "Failed to get default configuration");
     74    return NS_ERROR_FAILURE;
     75  }
     76 
     77  config->g_w = aWidth;
     78  config->g_h = aHeight;
     79  // TODO: Maybe we should have various aFrameRate bitrate pair for each
     80  // devices? or for different platform
     81 
     82  // rc_target_bitrate needs kbit/s
     83  config->rc_target_bitrate = std::max(
     84      1U, (aVideoBitrate != 0 ? aVideoBitrate : DEFAULT_BITRATE_BPS) / 1000);
     85 
     86  // Setting the time base of the codec
     87  config->g_timebase.num = 1;
     88  config->g_timebase.den = aTrackRate;
     89 
     90  // No error resilience as this is not intended for UDP transports
     91  config->g_error_resilient = 0;
     92 
     93  // Allow some frame lagging for large timeslices (when low latency is not
     94  // needed)
     95  /*std::min(10U, mKeyFrameInterval / 200)*/
     96  config->g_lag_in_frames = 0;
     97 
     98  int32_t number_of_cores = PR_GetNumberOfProcessors();
     99  if (aWidth * aHeight > 1920 * 1080 && number_of_cores >= 8) {
    100    config->g_threads = 4;  // 4 threads for > 1080p.
    101  } else if (aWidth * aHeight > 1280 * 960 && number_of_cores >= 6) {
    102    config->g_threads = 3;  // 3 threads for 1080p.
    103  } else if (aWidth * aHeight > 640 * 480 && number_of_cores >= 3) {
    104    config->g_threads = 2;  // 2 threads for qHD/HD.
    105  } else {
    106    config->g_threads = 1;  // 1 thread for VGA or less
    107  }
    108 
    109  // rate control settings
    110 
    111  // No frame dropping
    112  config->rc_dropframe_thresh = 0;
    113  // Variable bitrate
    114  config->rc_end_usage = VPX_VBR;
    115  // Single pass encoding
    116  config->g_pass = VPX_RC_ONE_PASS;
    117  // ffmpeg doesn't currently support streams that use resize.
    118  // Therefore, for safety, we should turn it off until it does.
    119  config->rc_resize_allowed = 0;
    120  // Allows 100% under target bitrate to compensate for prior overshoot
    121  config->rc_undershoot_pct = 100;
    122  // Allows 15% over target bitrate to compensate for prior undershoot
    123  config->rc_overshoot_pct = 15;
    124  // Tells the decoding application to buffer 500ms before beginning playback
    125  config->rc_buf_initial_sz = 500;
    126  // The decoding application will try to keep 600ms of buffer during playback
    127  config->rc_buf_optimal_sz = 600;
    128  // The decoding application may buffer 1000ms worth of encoded data
    129  config->rc_buf_sz = 1000;
    130 
    131  // We set key frame interval to automatic and try to set kf_max_dist so that
    132  // the encoder chooses to put keyframes slightly more often than
    133  // mKeyFrameInterval (which will encode with VPX_EFLAG_FORCE_KF when reached).
    134  config->kf_mode = VPX_KF_AUTO;
    135  config->kf_max_dist = aMaxKeyFrameDistance;
    136 
    137  return NS_OK;
    138 }
    139 }  // namespace
    140 
    141 VP8TrackEncoder::VP8TrackEncoder(RefPtr<DriftCompensator> aDriftCompensator,
    142                                 TrackRate aTrackRate,
    143                                 MediaQueue<EncodedFrame>& aEncodedDataQueue,
    144                                 FrameDroppingMode aFrameDroppingMode,
    145                                 Maybe<float> aKeyFrameIntervalFactor)
    146    : VideoTrackEncoder(std::move(aDriftCompensator), aTrackRate,
    147                        aEncodedDataQueue, aFrameDroppingMode),
    148      mKeyFrameInterval(
    149          TimeDuration::FromMilliseconds(DEFAULT_KEYFRAME_INTERVAL_MS)),
    150      mKeyFrameIntervalFactor(aKeyFrameIntervalFactor.valueOr(
    151          DYNAMIC_MAXKFDIST_KFINTERVAL_FACTOR)) {
    152  MOZ_COUNT_CTOR(VP8TrackEncoder);
    153  CalculateMaxKeyFrameDistance().apply(
    154      [&](auto aKfd) { SetMaxKeyFrameDistance(aKfd); });
    155 }
    156 
    157 VP8TrackEncoder::~VP8TrackEncoder() {
    158  Destroy();
    159  MOZ_COUNT_DTOR(VP8TrackEncoder);
    160 }
    161 
    162 void VP8TrackEncoder::Destroy() {
    163  if (mInitialized) {
    164    vpx_codec_destroy(&mVPXContext);
    165  }
    166 
    167  mInitialized = false;
    168 }
    169 
    170 Maybe<int32_t> VP8TrackEncoder::CalculateMaxKeyFrameDistance(
    171    Maybe<float> aEstimatedFrameRate /* = Nothing() */) const {
    172  if (!aEstimatedFrameRate && mMeanFrameDuration.empty()) {
    173    // Not enough data to make a new calculation.
    174    return Nothing();
    175  }
    176 
    177  // Calculate an estimation of our current framerate
    178  const float estimatedFrameRate = aEstimatedFrameRate.valueOrFrom(
    179      [&] { return 1.0f / mMeanFrameDuration.mean().ToSeconds(); });
    180  // Set a kf_max_dist that should avoid triggering the VPX_EFLAG_FORCE_KF flag
    181  return Some(std::max(
    182      1, static_cast<int32_t>(estimatedFrameRate * mKeyFrameIntervalFactor *
    183                              mKeyFrameInterval.ToSeconds())));
    184 }
    185 
    186 void VP8TrackEncoder::SetMaxKeyFrameDistance(int32_t aMaxKeyFrameDistance) {
    187  if (mInitialized) {
    188    VP8LOG(
    189        LogLevel::Debug,
    190        "%p SetMaxKeyFrameDistance() set kf_max_dist to %d based on estimated "
    191        "framerate %.2ffps keyframe-factor %.2f and keyframe-interval %.2fs",
    192        this, aMaxKeyFrameDistance, 1 / mMeanFrameDuration.mean().ToSeconds(),
    193        mKeyFrameIntervalFactor, mKeyFrameInterval.ToSeconds());
    194    DebugOnly<nsresult> rv =
    195        Reconfigure(mFrameWidth, mFrameHeight, aMaxKeyFrameDistance);
    196    MOZ_ASSERT(
    197        NS_SUCCEEDED(rv),
    198        "Reconfig for new key frame distance with proven size should succeed");
    199  } else {
    200    VP8LOG(LogLevel::Debug, "%p SetMaxKeyFrameDistance() distance=%d", this,
    201           aMaxKeyFrameDistance);
    202    mMaxKeyFrameDistance = Some(aMaxKeyFrameDistance);
    203  }
    204 }
    205 
    206 nsresult VP8TrackEncoder::Init(int32_t aWidth, int32_t aHeight,
    207                               int32_t aDisplayWidth, int32_t aDisplayHeight,
    208                               float aEstimatedFrameRate) {
    209  if (aDisplayWidth < 1 || aDisplayHeight < 1) {
    210    return NS_ERROR_FAILURE;
    211  }
    212 
    213  if (aEstimatedFrameRate <= 0) {
    214    return NS_ERROR_FAILURE;
    215  }
    216 
    217  int32_t maxKeyFrameDistance =
    218      *CalculateMaxKeyFrameDistance(Some(aEstimatedFrameRate));
    219 
    220  nsresult rv = InitInternal(aWidth, aHeight, maxKeyFrameDistance);
    221  NS_ENSURE_SUCCESS(rv, rv);
    222 
    223  MOZ_ASSERT(!mI420Frame);
    224  MOZ_ASSERT(mI420FrameSize == 0);
    225  const size_t neededSize = I420Size<I420_STRIDE_ALIGN>(aWidth, aHeight);
    226  mI420Frame.reset(new (fallible) uint8_t[neededSize]);
    227  mI420FrameSize = mI420Frame ? neededSize : 0;
    228  if (!mI420Frame) {
    229    VP8LOG(LogLevel::Warning, "Allocating I420 frame of size %zu failed",
    230           neededSize);
    231    return NS_ERROR_FAILURE;
    232  }
    233  vpx_img_wrap(&mVPXImageWrapper, VPX_IMG_FMT_I420, aWidth, aHeight,
    234               I420_STRIDE_ALIGN, mI420Frame.get());
    235 
    236  if (!mMetadata) {
    237    mMetadata = MakeAndAddRef<VP8Metadata>();
    238    mMetadata->mWidth = aWidth;
    239    mMetadata->mHeight = aHeight;
    240    mMetadata->mDisplayWidth = aDisplayWidth;
    241    mMetadata->mDisplayHeight = aDisplayHeight;
    242 
    243    VP8LOG(LogLevel::Info,
    244           "%p Init() created metadata. width=%d, height=%d, displayWidth=%d, "
    245           "displayHeight=%d, framerate=%.2f",
    246           this, mMetadata->mWidth, mMetadata->mHeight,
    247           mMetadata->mDisplayWidth, mMetadata->mDisplayHeight,
    248           aEstimatedFrameRate);
    249 
    250    SetInitialized();
    251  }
    252 
    253  return NS_OK;
    254 }
    255 
    256 nsresult VP8TrackEncoder::InitInternal(int32_t aWidth, int32_t aHeight,
    257                                       int32_t aMaxKeyFrameDistance) {
    258  if (aWidth < 1 || aHeight < 1) {
    259    return NS_ERROR_FAILURE;
    260  }
    261 
    262  if (mInitialized) {
    263    MOZ_ASSERT(false);
    264    return NS_ERROR_FAILURE;
    265  }
    266 
    267  VP8LOG(LogLevel::Debug,
    268         "%p InitInternal(). width=%d, height=%d, kf_max_dist=%d", this, aWidth,
    269         aHeight, aMaxKeyFrameDistance);
    270 
    271  // Encoder configuration structure.
    272  vpx_codec_enc_cfg_t config;
    273  nsresult rv = CreateEncoderConfig(aWidth, aHeight, mVideoBitrate, mTrackRate,
    274                                    aMaxKeyFrameDistance, &config);
    275  NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
    276 
    277  vpx_codec_flags_t flags = 0;
    278  flags |= VPX_CODEC_USE_OUTPUT_PARTITION;
    279  if (vpx_codec_enc_init(&mVPXContext, vpx_codec_vp8_cx(), &config, flags)) {
    280    return NS_ERROR_FAILURE;
    281  }
    282 
    283  vpx_codec_control(&mVPXContext, VP8E_SET_STATIC_THRESHOLD, 1);
    284  vpx_codec_control(&mVPXContext, VP8E_SET_CPUUSED, 15);
    285  vpx_codec_control(&mVPXContext, VP8E_SET_TOKEN_PARTITIONS,
    286                    VP8_TWO_TOKENPARTITION);
    287 
    288  mFrameWidth = aWidth;
    289  mFrameHeight = aHeight;
    290  mMaxKeyFrameDistance = Some(aMaxKeyFrameDistance);
    291 
    292  return NS_OK;
    293 }
    294 
    295 nsresult VP8TrackEncoder::Reconfigure(int32_t aWidth, int32_t aHeight,
    296                                      int32_t aMaxKeyFrameDistance) {
    297  if (aWidth <= 0 || aHeight <= 0) {
    298    MOZ_ASSERT(false);
    299    return NS_ERROR_FAILURE;
    300  }
    301 
    302  if (!mInitialized) {
    303    MOZ_ASSERT(false);
    304    return NS_ERROR_FAILURE;
    305  }
    306 
    307  bool needsReInit = aMaxKeyFrameDistance != *mMaxKeyFrameDistance;
    308 
    309  if (aWidth != mFrameWidth || aHeight != mFrameHeight) {
    310    VP8LOG(LogLevel::Info, "Dynamic resolution change (%dx%d -> %dx%d).",
    311           mFrameWidth, mFrameHeight, aWidth, aHeight);
    312    const size_t neededSize = I420Size<I420_STRIDE_ALIGN>(aWidth, aHeight);
    313    if (neededSize > mI420FrameSize) {
    314      needsReInit = true;
    315      mI420Frame.reset(new (fallible) uint8_t[neededSize]);
    316      mI420FrameSize = mI420Frame ? neededSize : 0;
    317    }
    318    if (!mI420Frame) {
    319      VP8LOG(LogLevel::Warning, "Allocating I420 frame of size %zu failed",
    320             neededSize);
    321      return NS_ERROR_FAILURE;
    322    }
    323    vpx_img_wrap(&mVPXImageWrapper, VPX_IMG_FMT_I420, aWidth, aHeight,
    324                 I420_STRIDE_ALIGN, mI420Frame.get());
    325  }
    326 
    327  if (needsReInit) {
    328    Destroy();
    329    mMaxKeyFrameDistance = Some(aMaxKeyFrameDistance);
    330    nsresult rv = InitInternal(aWidth, aHeight, aMaxKeyFrameDistance);
    331    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
    332    mInitialized = true;
    333    return NS_OK;
    334  }
    335 
    336  // Encoder configuration structure.
    337  vpx_codec_enc_cfg_t config;
    338  nsresult rv = CreateEncoderConfig(aWidth, aHeight, mVideoBitrate, mTrackRate,
    339                                    aMaxKeyFrameDistance, &config);
    340  NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
    341  // Set new configuration
    342  if (vpx_codec_enc_config_set(&mVPXContext, &config) != VPX_CODEC_OK) {
    343    VP8LOG(LogLevel::Error, "Failed to set new configuration");
    344    return NS_ERROR_FAILURE;
    345  }
    346 
    347  mFrameWidth = aWidth;
    348  mFrameHeight = aHeight;
    349 
    350  return NS_OK;
    351 }
    352 
    353 already_AddRefed<TrackMetadataBase> VP8TrackEncoder::GetMetadata() {
    354  AUTO_PROFILER_LABEL("VP8TrackEncoder::GetMetadata", OTHER);
    355 
    356  MOZ_ASSERT(mInitialized);
    357 
    358  if (!mInitialized) {
    359    return nullptr;
    360  }
    361 
    362  MOZ_ASSERT(mMetadata);
    363  return do_AddRef(mMetadata);
    364 }
    365 
    366 Result<RefPtr<EncodedFrame>, nsresult> VP8TrackEncoder::ExtractEncodedData() {
    367  vpx_codec_iter_t iter = nullptr;
    368  EncodedFrame::FrameType frameType = EncodedFrame::VP8_P_FRAME;
    369  auto frameData = MakeRefPtr<EncodedFrame::FrameData>();
    370  const vpx_codec_cx_pkt_t* pkt = nullptr;
    371  while ((pkt = vpx_codec_get_cx_data(&mVPXContext, &iter)) != nullptr) {
    372    switch (pkt->kind) {
    373      case VPX_CODEC_CX_FRAME_PKT: {
    374        // Copy the encoded data from libvpx to frameData
    375        frameData->AppendElements((uint8_t*)pkt->data.frame.buf,
    376                                  pkt->data.frame.sz);
    377        break;
    378      }
    379      default: {
    380        break;
    381      }
    382    }
    383    // End of frame
    384    if ((pkt->data.frame.flags & VPX_FRAME_IS_FRAGMENT) == 0) {
    385      if (pkt->data.frame.flags & VPX_FRAME_IS_KEY) {
    386        frameType = EncodedFrame::VP8_I_FRAME;
    387      }
    388      break;
    389    }
    390  }
    391 
    392  if (frameData->IsEmpty()) {
    393    return RefPtr<EncodedFrame>(nullptr);
    394  }
    395 
    396  if (!pkt) {
    397    // This check silences a coverity warning about accessing a null pkt below.
    398    return RefPtr<EncodedFrame>(nullptr);
    399  }
    400 
    401  if (pkt->data.frame.flags & VPX_FRAME_IS_KEY) {
    402    // Update the since-last-keyframe counter, and account for this frame's
    403    // time.
    404    TrackTime frameTime = pkt->data.frame.pts;
    405    DebugOnly<TrackTime> frameDuration = pkt->data.frame.duration;
    406    MOZ_ASSERT(frameTime + frameDuration <= mEncodedTimestamp);
    407    mDurationSinceLastKeyframe =
    408        std::min(mDurationSinceLastKeyframe, mEncodedTimestamp - frameTime);
    409  }
    410 
    411  // Convert the timestamp and duration to Usecs.
    412  media::TimeUnit timestamp = media::TimeUnit(pkt->data.frame.pts, mTrackRate);
    413  if (!timestamp.IsValid()) {
    414    NS_ERROR("Microsecond timestamp overflow");
    415    return Err(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR);
    416  }
    417 
    418  mExtractedDuration += pkt->data.frame.duration;
    419  if (!mExtractedDuration.isValid()) {
    420    NS_ERROR("Duration overflow");
    421    return Err(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR);
    422  }
    423 
    424  media::TimeUnit totalDuration =
    425      media::TimeUnit(mExtractedDuration.value(), mTrackRate);
    426  if (!totalDuration.IsValid()) {
    427    NS_ERROR("Duration overflow");
    428    return Err(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR);
    429  }
    430 
    431  media::TimeUnit duration = totalDuration - mExtractedDurationUs;
    432  if (!duration.IsValid()) {
    433    NS_ERROR("Duration overflow");
    434    return Err(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR);
    435  }
    436 
    437  mExtractedDurationUs = totalDuration;
    438 
    439  VP8LOG(LogLevel::Verbose,
    440         "ExtractEncodedData TimeStamp %.2f, Duration %.2f, FrameType %d",
    441         timestamp.ToSeconds(), duration.ToSeconds(), frameType);
    442 
    443  if (static_cast<int>(totalDuration.ToSeconds()) /
    444          DYNAMIC_MAXKFDIST_CHECK_INTERVAL >
    445      static_cast<int>(mLastKeyFrameDistanceUpdate.ToSeconds()) /
    446          DYNAMIC_MAXKFDIST_CHECK_INTERVAL) {
    447    // The interval has passed since the last keyframe update. Update again.
    448    mLastKeyFrameDistanceUpdate = totalDuration;
    449    const int32_t maxKfDistance =
    450        CalculateMaxKeyFrameDistance().valueOr(*mMaxKeyFrameDistance);
    451    const float diffFactor =
    452        static_cast<float>(maxKfDistance) / *mMaxKeyFrameDistance;
    453    VP8LOG(LogLevel::Debug, "maxKfDistance: %d, factor: %.2f", maxKfDistance,
    454           diffFactor);
    455    if (std::abs(1.0 - diffFactor) > DYNAMIC_MAXKFDIST_DIFFACTOR) {
    456      SetMaxKeyFrameDistance(maxKfDistance);
    457    }
    458  }
    459 
    460  return MakeRefPtr<EncodedFrame>(timestamp, duration.ToMicroseconds(),
    461                                  PR_USEC_PER_SEC, frameType,
    462                                  std::move(frameData));
    463 }
    464 
    465 /**
    466 * Encoding flow in Encode():
    467 * 1: Assert valid state.
    468 * 2: Encode the video chunks in mSourceSegment in a for-loop.
    469 * 2.1: The duration is taken straight from the video chunk's duration.
    470 * 2.2: Setup the video chunk with mVPXImageWrapper by PrepareRawFrame().
    471 * 2.3: Pass frame to vp8 encoder by vpx_codec_encode().
    472 * 2.4: Extract the encoded frame from encoder by ExtractEncodedData().
    473 * 2.5: Set the nextEncodeOperation for the next frame.
    474 * 2.6: If we are not skipping the next frame, add the encoded frame to
    475 *      mEncodedDataQueue. If we are skipping the next frame, extend the encoded
    476 *      frame's duration in the next run of the loop.
    477 * 3. Clear aSegment.
    478 */
    479 nsresult VP8TrackEncoder::Encode(VideoSegment* aSegment) {
    480  MOZ_ASSERT(mInitialized);
    481  MOZ_ASSERT(!IsEncodingComplete());
    482 
    483  AUTO_PROFILER_LABEL("VP8TrackEncoder::Encode", OTHER);
    484 
    485  EncodeOperation nextEncodeOperation = ENCODE_NORMAL_FRAME;
    486 
    487  RefPtr<EncodedFrame> encodedFrame;
    488  for (VideoSegment::ChunkIterator iter(*aSegment); !iter.IsEnded();
    489       iter.Next()) {
    490    VideoChunk& chunk = *iter;
    491 
    492    VP8LOG(LogLevel::Verbose,
    493           "nextEncodeOperation is %d for frame of duration %" PRId64,
    494           nextEncodeOperation, chunk.GetDuration());
    495 
    496    TimeStamp timebase = TimeStamp::Now();
    497 
    498    // Encode frame.
    499    if (nextEncodeOperation != SKIP_FRAME) {
    500      MOZ_ASSERT(!encodedFrame);
    501      nsresult rv = PrepareRawFrame(chunk);
    502      NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
    503 
    504      // Encode the data with VP8 encoder
    505      int flags = 0;
    506      if (nextEncodeOperation == ENCODE_I_FRAME) {
    507        VP8LOG(LogLevel::Warning,
    508               "MediaRecorder lagging behind. Encoding keyframe.");
    509        flags |= VPX_EFLAG_FORCE_KF;
    510      }
    511 
    512      // Sum duration of non-key frames and force keyframe if exceeded the
    513      // given keyframe interval
    514      if (mKeyFrameInterval > TimeDuration::FromSeconds(0)) {
    515        if (media::TimeUnit(mDurationSinceLastKeyframe, mTrackRate)
    516                .ToTimeDuration() >= mKeyFrameInterval) {
    517          VP8LOG(LogLevel::Warning,
    518                 "Reached mKeyFrameInterval without seeing a keyframe. Forcing "
    519                 "one. time: %.2f, interval: %.2f",
    520                 media::TimeUnit(mDurationSinceLastKeyframe, mTrackRate)
    521                     .ToSeconds(),
    522                 mKeyFrameInterval.ToSeconds());
    523          mDurationSinceLastKeyframe = 0;
    524          flags |= VPX_EFLAG_FORCE_KF;
    525        }
    526        mDurationSinceLastKeyframe += chunk.GetDuration();
    527      }
    528 
    529      if (vpx_codec_encode(&mVPXContext, &mVPXImageWrapper, mEncodedTimestamp,
    530                           (unsigned long)chunk.GetDuration(), flags,
    531                           VPX_DL_REALTIME)) {
    532        VP8LOG(LogLevel::Error, "vpx_codec_encode failed to encode the frame.");
    533        return NS_ERROR_FAILURE;
    534      }
    535 
    536      // Move forward the mEncodedTimestamp.
    537      mEncodedTimestamp += chunk.GetDuration();
    538 
    539      // Extract the encoded data from the underlying encoder and push it to
    540      // mEncodedDataQueue.
    541      auto result = ExtractEncodedData();
    542      if (result.isErr()) {
    543        VP8LOG(LogLevel::Error, "ExtractEncodedData failed.");
    544        return NS_ERROR_FAILURE;
    545      }
    546 
    547      MOZ_ASSERT(result.inspect(),
    548                 "We expected a frame here. EOS is handled explicitly later");
    549      encodedFrame = result.unwrap();
    550    } else {
    551      // SKIP_FRAME
    552 
    553      MOZ_DIAGNOSTIC_ASSERT(encodedFrame);
    554 
    555      if (mKeyFrameInterval > TimeDuration::FromSeconds(0)) {
    556        mDurationSinceLastKeyframe += chunk.GetDuration();
    557      }
    558 
    559      // Move forward the mEncodedTimestamp.
    560      mEncodedTimestamp += chunk.GetDuration();
    561 
    562      // Extend the duration of the last encoded frame in mEncodedDataQueue
    563      // because this frame will be skipped.
    564      VP8LOG(LogLevel::Warning,
    565             "MediaRecorder lagging behind. Skipping a frame.");
    566 
    567      mExtractedDuration += chunk.mDuration;
    568      if (!mExtractedDuration.isValid()) {
    569        NS_ERROR("skipped duration overflow");
    570        return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR;
    571      }
    572 
    573      media::TimeUnit totalDuration =
    574          media::TimeUnit(mExtractedDuration.value(), mTrackRate);
    575      media::TimeUnit skippedDuration = totalDuration - mExtractedDurationUs;
    576      mExtractedDurationUs = totalDuration;
    577      if (!skippedDuration.IsValid()) {
    578        NS_ERROR("skipped duration overflow");
    579        return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR;
    580      }
    581 
    582      encodedFrame = MakeRefPtr<EncodedFrame>(
    583          encodedFrame->mTime,
    584          encodedFrame->mDuration + skippedDuration.ToMicroseconds(),
    585          encodedFrame->mDurationBase, encodedFrame->mFrameType,
    586          encodedFrame->mFrameData);
    587    }
    588 
    589    mMeanFrameEncodeDuration.insert(TimeStamp::Now() - timebase);
    590    mMeanFrameDuration.insert(
    591        media::TimeUnit(chunk.GetDuration(), mTrackRate).ToTimeDuration());
    592    nextEncodeOperation = GetNextEncodeOperation(
    593        mMeanFrameEncodeDuration.mean(), mMeanFrameDuration.mean());
    594 
    595    if (nextEncodeOperation != SKIP_FRAME) {
    596      // Note that the next operation might be SKIP_FRAME even if there is no
    597      // next frame.
    598      mEncodedDataQueue.Push(encodedFrame.forget());
    599    }
    600  }
    601 
    602  if (encodedFrame) {
    603    // Push now if we ended on a SKIP_FRAME before.
    604    mEncodedDataQueue.Push(encodedFrame.forget());
    605  }
    606 
    607  // Remove the chunks we have processed.
    608  aSegment->Clear();
    609 
    610  if (mEndOfStream) {
    611    // EOS: Extract the remaining frames from the underlying encoder.
    612    VP8LOG(LogLevel::Debug, "mEndOfStream is true");
    613    // No more frames will be encoded. Clearing temporary frames saves some
    614    // memory.
    615    if (mI420Frame) {
    616      mI420Frame = nullptr;
    617      mI420FrameSize = 0;
    618    }
    619    // mMuteFrame must be released before gfx shutdown. We do it now since it
    620    // may be too late when this VP8TrackEncoder gets destroyed.
    621    mMuteFrame = nullptr;
    622    // Bug 1243611, keep calling vpx_codec_encode and vpx_codec_get_cx_data
    623    // until vpx_codec_get_cx_data return null.
    624    while (true) {
    625      if (vpx_codec_encode(&mVPXContext, nullptr, mEncodedTimestamp, 0, 0,
    626                           VPX_DL_REALTIME)) {
    627        return NS_ERROR_FAILURE;
    628      }
    629      auto result = ExtractEncodedData();
    630      if (result.isErr()) {
    631        return NS_ERROR_FAILURE;
    632      }
    633      if (!result.inspect()) {
    634        // Null means end-of-stream.
    635        break;
    636      }
    637      mEncodedDataQueue.Push(result.unwrap().forget());
    638    }
    639    mEncodedDataQueue.Finish();
    640  }
    641 
    642  return NS_OK;
    643 }
    644 
    645 nsresult VP8TrackEncoder::PrepareRawFrame(VideoChunk& aChunk) {
    646  gfx::IntSize intrinsicSize = aChunk.mFrame.GetIntrinsicSize();
    647  RefPtr<Image> img;
    648  if (aChunk.mFrame.GetForceBlack() || aChunk.IsNull()) {
    649    if (!mMuteFrame || mMuteFrame->GetSize() != intrinsicSize) {
    650      mMuteFrame = mozilla::VideoFrame::CreateBlackImage(intrinsicSize);
    651    }
    652    if (!mMuteFrame) {
    653      VP8LOG(LogLevel::Warning, "Failed to allocate black image of size %dx%d",
    654             intrinsicSize.width, intrinsicSize.height);
    655      return NS_OK;
    656    }
    657    img = mMuteFrame;
    658  } else {
    659    img = aChunk.mFrame.GetImage();
    660  }
    661 
    662  gfx::IntSize imgSize = img->GetSize();
    663  if (imgSize != IntSize(mFrameWidth, mFrameHeight)) {
    664    nsresult rv =
    665        Reconfigure(imgSize.width, imgSize.height, *mMaxKeyFrameDistance);
    666    NS_ENSURE_SUCCESS(rv, rv);
    667  }
    668 
    669  MOZ_ASSERT(mFrameWidth == imgSize.width);
    670  MOZ_ASSERT(mFrameHeight == imgSize.height);
    671 
    672  nsresult rv = ConvertToI420(img, mVPXImageWrapper.planes[VPX_PLANE_Y],
    673                              mVPXImageWrapper.stride[VPX_PLANE_Y],
    674                              mVPXImageWrapper.planes[VPX_PLANE_U],
    675                              mVPXImageWrapper.stride[VPX_PLANE_U],
    676                              mVPXImageWrapper.planes[VPX_PLANE_V],
    677                              mVPXImageWrapper.stride[VPX_PLANE_V], imgSize);
    678  if (NS_FAILED(rv)) {
    679    VP8LOG(LogLevel::Error, "Converting to I420 failed");
    680    return rv;
    681  }
    682 
    683  return NS_OK;
    684 }
    685 
    686 // These two define value used in GetNextEncodeOperation to determine the
    687 // EncodeOperation for next target frame.
    688 #define I_FRAME_RATIO (0.85)  // Effectively disabled, because perceived quality
    689 #define SKIP_FRAME_RATIO (0.85)
    690 
    691 /**
    692 * Compares the elapsed time from the beginning of GetEncodedTrack and
    693 * the processed frame duration in mSourceSegment
    694 * in order to set the nextEncodeOperation for next target frame.
    695 */
    696 VP8TrackEncoder::EncodeOperation VP8TrackEncoder::GetNextEncodeOperation(
    697    TimeDuration aTimeElapsed, TimeDuration aProcessedDuration) {
    698  if (mFrameDroppingMode == FrameDroppingMode::DISALLOW) {
    699    return ENCODE_NORMAL_FRAME;
    700  }
    701 
    702  if (aTimeElapsed.ToSeconds() >
    703      aProcessedDuration.ToSeconds() * SKIP_FRAME_RATIO) {
    704    // The encoder is too slow.
    705    // We should skip next frame to consume the mSourceSegment.
    706    return SKIP_FRAME;
    707  }
    708 
    709  if (aTimeElapsed.ToSeconds() >
    710      aProcessedDuration.ToSeconds() * I_FRAME_RATIO) {
    711    // The encoder is a little slow.
    712    // We force the encoder to encode an I-frame to accelerate.
    713    return ENCODE_I_FRAME;
    714  }
    715 
    716  return ENCODE_NORMAL_FRAME;
    717 }
    718 
    719 }  // namespace mozilla
    720 
    721 #undef VP8LOG