tor-browser

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

TrackEncoder.cpp (26530B)


      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 "TrackEncoder.h"
      7 
      8 #include "AudioChannelFormat.h"
      9 #include "DriftCompensation.h"
     10 #include "MediaTrackGraph.h"
     11 #include "MediaTrackListener.h"
     12 #include "VideoUtils.h"
     13 #include "mozilla/AbstractThread.h"
     14 #include "mozilla/Logging.h"
     15 #include "mozilla/ProfilerLabels.h"
     16 #include "mozilla/RollingMean.h"
     17 
     18 namespace mozilla {
     19 
     20 LazyLogModule gTrackEncoderLog("TrackEncoder");
     21 #define TRACK_LOG(type, msg) MOZ_LOG(gTrackEncoderLog, type, msg)
     22 
     23 constexpr int DEFAULT_CHANNELS = 1;
     24 constexpr int DEFAULT_FRAME_WIDTH = 640;
     25 constexpr int DEFAULT_FRAME_HEIGHT = 480;
     26 constexpr int DEFAULT_FRAME_RATE = 30;
     27 // 10 second threshold if the audio encoder cannot be initialized.
     28 constexpr int AUDIO_INIT_FAILED_DURATION = 10;
     29 // 30 second threshold if the video encoder cannot be initialized.
     30 constexpr int VIDEO_INIT_FAILED_DURATION = 30;
     31 constexpr int FRAMERATE_DETECTION_ROLLING_WINDOW = 3;
     32 constexpr size_t FRAMERATE_DETECTION_MIN_CHUNKS = 5;
     33 constexpr int FRAMERATE_DETECTION_MAX_DURATION_S = 6;
     34 
     35 TrackEncoder::TrackEncoder(TrackRate aTrackRate,
     36                           MediaQueue<EncodedFrame>& aEncodedDataQueue)
     37    : mInitialized(false),
     38      mStarted(false),
     39      mEndOfStream(false),
     40      mCanceled(false),
     41      mInitCounter(0),
     42      mSuspended(false),
     43      mTrackRate(aTrackRate),
     44      mEncodedDataQueue(aEncodedDataQueue) {}
     45 
     46 bool TrackEncoder::IsInitialized() {
     47  MOZ_ASSERT(!mWorkerThread || mWorkerThread->IsCurrentThreadIn());
     48  return mInitialized;
     49 }
     50 
     51 bool TrackEncoder::IsStarted() {
     52  MOZ_ASSERT(!mWorkerThread || mWorkerThread->IsCurrentThreadIn());
     53  return mStarted;
     54 }
     55 
     56 bool TrackEncoder::IsEncodingComplete() const {
     57  MOZ_ASSERT(!mWorkerThread || mWorkerThread->IsCurrentThreadIn());
     58  return mEncodedDataQueue.IsFinished();
     59 }
     60 
     61 void TrackEncoder::SetInitialized() {
     62  MOZ_ASSERT(!mWorkerThread || mWorkerThread->IsCurrentThreadIn());
     63 
     64  if (mInitialized) {
     65    return;
     66  }
     67 
     68  mInitialized = true;
     69 
     70  for (auto& l : mListeners.Clone()) {
     71    l->Initialized(this);
     72  }
     73 }
     74 
     75 void TrackEncoder::SetStarted() {
     76  MOZ_ASSERT(!mWorkerThread || mWorkerThread->IsCurrentThreadIn());
     77 
     78  if (mStarted) {
     79    return;
     80  }
     81 
     82  mStarted = true;
     83 
     84  for (auto& l : mListeners.Clone()) {
     85    l->Started(this);
     86  }
     87 }
     88 
     89 void TrackEncoder::OnError() {
     90  MOZ_ASSERT(!mWorkerThread || mWorkerThread->IsCurrentThreadIn());
     91 
     92  Cancel();
     93 
     94  for (auto& l : mListeners.Clone()) {
     95    l->Error(this);
     96  }
     97 }
     98 
     99 void TrackEncoder::RegisterListener(TrackEncoderListener* aListener) {
    100  MOZ_ASSERT(!mWorkerThread || mWorkerThread->IsCurrentThreadIn());
    101  MOZ_ASSERT(!mListeners.Contains(aListener));
    102  mListeners.AppendElement(aListener);
    103 }
    104 
    105 bool TrackEncoder::UnregisterListener(TrackEncoderListener* aListener) {
    106  MOZ_ASSERT(!mWorkerThread || mWorkerThread->IsCurrentThreadIn());
    107  return mListeners.RemoveElement(aListener);
    108 }
    109 
    110 void TrackEncoder::SetWorkerThread(AbstractThread* aWorkerThread) {
    111  mWorkerThread = aWorkerThread;
    112 }
    113 
    114 void AudioTrackEncoder::Suspend() {
    115  MOZ_ASSERT(!mWorkerThread || mWorkerThread->IsCurrentThreadIn());
    116  TRACK_LOG(LogLevel::Info, ("[AudioTrackEncoder %p]: Suspend(), was %s", this,
    117                             mSuspended ? "suspended" : "live"));
    118 
    119  if (mSuspended) {
    120    return;
    121  }
    122 
    123  mSuspended = true;
    124 }
    125 
    126 void AudioTrackEncoder::Resume() {
    127  MOZ_ASSERT(!mWorkerThread || mWorkerThread->IsCurrentThreadIn());
    128  TRACK_LOG(LogLevel::Info, ("[AudioTrackEncoder %p]: Resume(), was %s", this,
    129                             mSuspended ? "suspended" : "live"));
    130 
    131  if (!mSuspended) {
    132    return;
    133  }
    134 
    135  mSuspended = false;
    136 }
    137 
    138 void AudioTrackEncoder::AppendAudioSegment(AudioSegment&& aSegment) {
    139  MOZ_ASSERT(!mWorkerThread || mWorkerThread->IsCurrentThreadIn());
    140  AUTO_PROFILER_LABEL("AudioTrackEncoder::AppendAudioSegment", OTHER);
    141  TRACK_LOG(LogLevel::Verbose,
    142            ("[AudioTrackEncoder %p]: AppendAudioSegment() duration=%" PRIu64,
    143             this, aSegment.GetDuration()));
    144 
    145  if (mCanceled) {
    146    return;
    147  }
    148 
    149  if (mEndOfStream) {
    150    return;
    151  }
    152 
    153  TryInit(mOutgoingBuffer, aSegment.GetDuration());
    154 
    155  if (mSuspended) {
    156    return;
    157  }
    158 
    159  SetStarted();
    160  mOutgoingBuffer.AppendFrom(&aSegment);
    161 
    162  if (!mInitialized) {
    163    return;
    164  }
    165 
    166  if (NS_FAILED(Encode(&mOutgoingBuffer))) {
    167    OnError();
    168    return;
    169  }
    170 
    171  MOZ_ASSERT_IF(IsEncodingComplete(), mOutgoingBuffer.IsEmpty());
    172 }
    173 
    174 void AudioTrackEncoder::TryInit(const AudioSegment& aSegment,
    175                                TrackTime aDuration) {
    176  MOZ_ASSERT(!mWorkerThread || mWorkerThread->IsCurrentThreadIn());
    177 
    178  if (mInitialized) {
    179    return;
    180  }
    181 
    182  mInitCounter++;
    183  TRACK_LOG(LogLevel::Debug,
    184            ("[AudioTrackEncoder %p]: Inited the audio encoder %d times", this,
    185             mInitCounter));
    186 
    187  for (AudioSegment::ConstChunkIterator iter(aSegment); !iter.IsEnded();
    188       iter.Next()) {
    189    // The number of channels is determined by the first non-null chunk, and
    190    // thus the audio encoder is initialized at this time.
    191    if (iter->IsNull()) {
    192      continue;
    193    }
    194 
    195    nsresult rv = Init(iter->mChannelData.Length());
    196 
    197    if (NS_SUCCEEDED(rv)) {
    198      TRACK_LOG(LogLevel::Info,
    199                ("[AudioTrackEncoder %p]: Successfully initialized!", this));
    200      return;
    201    } else {
    202      TRACK_LOG(
    203          LogLevel::Error,
    204          ("[AudioTrackEncoder %p]: Failed to initialize the encoder!", this));
    205      OnError();
    206      return;
    207    }
    208    break;
    209  }
    210 
    211  mNotInitDuration += aDuration;
    212  if (!mInitialized &&
    213      ((mNotInitDuration - 1) / mTrackRate >= AUDIO_INIT_FAILED_DURATION) &&
    214      mInitCounter > 1) {
    215    // Perform a best effort initialization since we haven't gotten any
    216    // data yet. Motivated by issues like Bug 1336367
    217    TRACK_LOG(LogLevel::Warning,
    218              ("[AudioTrackEncoder]: Initialize failed for %ds. Attempting to "
    219               "init with %d (default) channels!",
    220               AUDIO_INIT_FAILED_DURATION, DEFAULT_CHANNELS));
    221    nsresult rv = Init(DEFAULT_CHANNELS);
    222    if (NS_FAILED(rv)) {
    223      TRACK_LOG(LogLevel::Error,
    224                ("[AudioTrackEncoder %p]: Default-channel-init failed.", this));
    225      OnError();
    226      return;
    227    }
    228  }
    229 }
    230 
    231 void AudioTrackEncoder::Cancel() {
    232  MOZ_ASSERT(!mWorkerThread || mWorkerThread->IsCurrentThreadIn());
    233  TRACK_LOG(LogLevel::Info, ("[AudioTrackEncoder %p]: Cancel()", this));
    234  mCanceled = true;
    235  mEndOfStream = true;
    236  mOutgoingBuffer.Clear();
    237  mEncodedDataQueue.Finish();
    238 }
    239 
    240 void AudioTrackEncoder::NotifyEndOfStream() {
    241  MOZ_ASSERT(!mWorkerThread || mWorkerThread->IsCurrentThreadIn());
    242  TRACK_LOG(LogLevel::Info,
    243            ("[AudioTrackEncoder %p]: NotifyEndOfStream()", this));
    244 
    245  if (!mCanceled && !mInitialized) {
    246    // If source audio track is completely silent till the end of encoding,
    247    // initialize the encoder with a default channel count.
    248    Init(DEFAULT_CHANNELS);
    249  }
    250 
    251  if (mEndOfStream) {
    252    return;
    253  }
    254 
    255  mEndOfStream = true;
    256 
    257  if (NS_FAILED(Encode(&mOutgoingBuffer))) {
    258    mOutgoingBuffer.Clear();
    259    OnError();
    260  }
    261 
    262  MOZ_ASSERT(mOutgoingBuffer.GetDuration() == 0);
    263 }
    264 
    265 /*static*/
    266 void AudioTrackEncoder::InterleaveTrackData(AudioChunk& aChunk,
    267                                            int32_t aDuration,
    268                                            uint32_t aOutputChannels,
    269                                            AudioDataValue* aOutput) {
    270  uint32_t numChannelsToCopy = std::min(
    271      aOutputChannels, static_cast<uint32_t>(aChunk.mChannelData.Length()));
    272  switch (aChunk.mBufferFormat) {
    273    case AUDIO_FORMAT_S16: {
    274      AutoTArray<const int16_t*, 2> array;
    275      array.SetLength(numChannelsToCopy);
    276      for (uint32_t i = 0; i < array.Length(); i++) {
    277        array[i] = static_cast<const int16_t*>(aChunk.mChannelData[i]);
    278      }
    279      InterleaveTrackData(array, aDuration, aOutputChannels, aOutput,
    280                          aChunk.mVolume);
    281      break;
    282    }
    283    case AUDIO_FORMAT_FLOAT32: {
    284      AutoTArray<const float*, 2> array;
    285      array.SetLength(numChannelsToCopy);
    286      for (uint32_t i = 0; i < array.Length(); i++) {
    287        array[i] = static_cast<const float*>(aChunk.mChannelData[i]);
    288      }
    289      InterleaveTrackData(array, aDuration, aOutputChannels, aOutput,
    290                          aChunk.mVolume);
    291      break;
    292    }
    293    case AUDIO_FORMAT_SILENCE: {
    294      MOZ_ASSERT(false, "To implement.");
    295    }
    296  };
    297 }
    298 
    299 /*static*/
    300 void AudioTrackEncoder::DeInterleaveTrackData(AudioDataValue* aInput,
    301                                              int32_t aDuration,
    302                                              int32_t aChannels,
    303                                              AudioDataValue* aOutput) {
    304  for (int32_t i = 0; i < aChannels; ++i) {
    305    for (int32_t j = 0; j < aDuration; ++j) {
    306      aOutput[i * aDuration + j] = aInput[i + j * aChannels];
    307    }
    308  }
    309 }
    310 
    311 size_t AudioTrackEncoder::SizeOfExcludingThis(
    312    mozilla::MallocSizeOf aMallocSizeOf) {
    313  MOZ_ASSERT(!mWorkerThread || mWorkerThread->IsCurrentThreadIn());
    314  return mOutgoingBuffer.SizeOfExcludingThis(aMallocSizeOf);
    315 }
    316 
    317 VideoTrackEncoder::VideoTrackEncoder(
    318    RefPtr<DriftCompensator> aDriftCompensator, TrackRate aTrackRate,
    319    MediaQueue<EncodedFrame>& aEncodedDataQueue,
    320    FrameDroppingMode aFrameDroppingMode)
    321    : TrackEncoder(aTrackRate, aEncodedDataQueue),
    322      mDriftCompensator(std::move(aDriftCompensator)),
    323      mEncodedTicks(0),
    324      mVideoBitrate(0),
    325      mFrameDroppingMode(aFrameDroppingMode),
    326      mEnabled(true) {
    327  mLastChunk.mDuration = 0;
    328 }
    329 
    330 void VideoTrackEncoder::Suspend(const TimeStamp& aTime) {
    331  MOZ_ASSERT(!mWorkerThread || mWorkerThread->IsCurrentThreadIn());
    332  TRACK_LOG(LogLevel::Info,
    333            ("[VideoTrackEncoder %p]: Suspend() at %.3fs, was %s", this,
    334             mStartTime.IsNull() ? 0.0 : (aTime - mStartTime).ToSeconds(),
    335             mSuspended ? "suspended" : "live"));
    336 
    337  if (mSuspended) {
    338    return;
    339  }
    340 
    341  mSuspended = true;
    342  mSuspendTime = aTime;
    343 }
    344 
    345 void VideoTrackEncoder::Resume(const TimeStamp& aTime) {
    346  MOZ_ASSERT(!mWorkerThread || mWorkerThread->IsCurrentThreadIn());
    347 
    348  if (!mSuspended) {
    349    return;
    350  }
    351 
    352  TRACK_LOG(
    353      LogLevel::Info,
    354      ("[VideoTrackEncoder %p]: Resume() after %.3fs, was %s", this,
    355       (aTime - mSuspendTime).ToSeconds(), mSuspended ? "suspended" : "live"));
    356 
    357  mSuspended = false;
    358 
    359  TimeDuration suspendDuration = aTime - mSuspendTime;
    360  if (!mLastChunk.mTimeStamp.IsNull()) {
    361    VideoChunk* nextChunk = mIncomingBuffer.FindChunkContaining(aTime);
    362    MOZ_ASSERT_IF(nextChunk, nextChunk->mTimeStamp <= aTime);
    363    if (nextChunk) {
    364      nextChunk->mTimeStamp = aTime;
    365    }
    366    mLastChunk.mTimeStamp += suspendDuration;
    367  }
    368  if (!mStartTime.IsNull()) {
    369    mStartTime += suspendDuration;
    370  }
    371 
    372  mSuspendTime = TimeStamp();
    373 }
    374 
    375 void VideoTrackEncoder::Disable(const TimeStamp& aTime) {
    376  MOZ_ASSERT(!mWorkerThread || mWorkerThread->IsCurrentThreadIn());
    377  TRACK_LOG(LogLevel::Debug, ("[VideoTrackEncoder %p]: Disable()", this));
    378 
    379  if (mStartTime.IsNull()) {
    380    // We haven't started yet. No need to touch future frames.
    381    mEnabled = false;
    382    return;
    383  }
    384 
    385  // Advancing currentTime to process any frames in mIncomingBuffer between
    386  // mCurrentTime and aTime.
    387  AdvanceCurrentTime(aTime);
    388  if (!mLastChunk.mTimeStamp.IsNull()) {
    389    // Insert a black frame at t=aTime into mIncomingBuffer, to trigger the
    390    // shift to black at the right moment.
    391    VideoSegment tempSegment;
    392    tempSegment.AppendFrom(&mIncomingBuffer);
    393    mIncomingBuffer.AppendFrame(do_AddRef(mLastChunk.mFrame.GetImage()),
    394                                mLastChunk.mFrame.GetIntrinsicSize(),
    395                                mLastChunk.mFrame.GetPrincipalHandle(), true,
    396                                aTime);
    397    mIncomingBuffer.AppendFrom(&tempSegment);
    398  }
    399  mEnabled = false;
    400 }
    401 
    402 void VideoTrackEncoder::Enable(const TimeStamp& aTime) {
    403  MOZ_ASSERT(!mWorkerThread || mWorkerThread->IsCurrentThreadIn());
    404  TRACK_LOG(LogLevel::Debug, ("[VideoTrackEncoder %p]: Enable()", this));
    405 
    406  if (mStartTime.IsNull()) {
    407    // We haven't started yet. No need to touch future frames.
    408    mEnabled = true;
    409    return;
    410  }
    411 
    412  // Advancing currentTime to process any frames in mIncomingBuffer between
    413  // mCurrentTime and aTime.
    414  AdvanceCurrentTime(aTime);
    415  if (!mLastChunk.mTimeStamp.IsNull()) {
    416    // Insert a real frame at t=aTime into mIncomingBuffer, to trigger the
    417    // shift from black at the right moment.
    418    VideoSegment tempSegment;
    419    tempSegment.AppendFrom(&mIncomingBuffer);
    420    mIncomingBuffer.AppendFrame(do_AddRef(mLastChunk.mFrame.GetImage()),
    421                                mLastChunk.mFrame.GetIntrinsicSize(),
    422                                mLastChunk.mFrame.GetPrincipalHandle(),
    423                                mLastChunk.mFrame.GetForceBlack(), aTime);
    424    mIncomingBuffer.AppendFrom(&tempSegment);
    425  }
    426  mEnabled = true;
    427 }
    428 
    429 void VideoTrackEncoder::AppendVideoSegment(VideoSegment&& aSegment) {
    430  MOZ_ASSERT(!mWorkerThread || mWorkerThread->IsCurrentThreadIn());
    431  TRACK_LOG(LogLevel::Verbose,
    432            ("[VideoTrackEncoder %p]: AppendVideoSegment()", this));
    433 
    434  if (mCanceled) {
    435    return;
    436  }
    437 
    438  if (mEndOfStream) {
    439    return;
    440  }
    441 
    442  for (VideoSegment::ConstChunkIterator iter(aSegment); !iter.IsEnded();
    443       iter.Next()) {
    444    if (iter->IsNull()) {
    445      // A null image was sent. This is a signal from the source that we should
    446      // clear any images buffered in the future.
    447      mIncomingBuffer.Clear();
    448      continue;  // Don't append iter, as it is null.
    449    }
    450    if (VideoChunk* c = mIncomingBuffer.GetLastChunk()) {
    451      if (iter->mTimeStamp < c->mTimeStamp) {
    452        // Time went backwards. This can happen when a MediaDecoder seeks.
    453        // We need to handle this by removing any frames buffered in the future
    454        // and start over at iter->mTimeStamp.
    455        mIncomingBuffer.Clear();
    456      }
    457    }
    458    SetStarted();
    459    mIncomingBuffer.AppendFrame(do_AddRef(iter->mFrame.GetImage()),
    460                                iter->mFrame.GetIntrinsicSize(),
    461                                iter->mFrame.GetPrincipalHandle(),
    462                                iter->mFrame.GetForceBlack(), iter->mTimeStamp);
    463  }
    464  aSegment.Clear();
    465 }
    466 
    467 void VideoTrackEncoder::Init(const VideoSegment& aSegment,
    468                             const TimeStamp& aTime,
    469                             size_t aFrameRateDetectionMinChunks) {
    470  MOZ_ASSERT(!mWorkerThread || mWorkerThread->IsCurrentThreadIn());
    471  MOZ_ASSERT(!aTime.IsNull());
    472 
    473  if (mInitialized) {
    474    return;
    475  }
    476 
    477  mInitCounter++;
    478  TRACK_LOG(LogLevel::Debug,
    479            ("[VideoTrackEncoder %p]: Init the video encoder %d times", this,
    480             mInitCounter));
    481 
    482  Maybe<float> framerate;
    483  if (!aSegment.IsEmpty()) {
    484    // The number of whole frames, i.e., with known duration.
    485    size_t frameCount = 0;
    486    RollingMean<TimeDuration, TimeDuration> meanDuration(
    487        FRAMERATE_DETECTION_ROLLING_WINDOW);
    488    VideoSegment::ConstChunkIterator iter(aSegment);
    489    TimeStamp previousChunkTime = iter->mTimeStamp;
    490    iter.Next();
    491    for (; !iter.IsEnded(); iter.Next(), ++frameCount) {
    492      meanDuration.insert(iter->mTimeStamp - previousChunkTime);
    493      previousChunkTime = iter->mTimeStamp;
    494    }
    495    TRACK_LOG(LogLevel::Debug, ("[VideoTrackEncoder %p]: Init() frameCount=%zu",
    496                                this, frameCount));
    497    if (frameCount >= aFrameRateDetectionMinChunks) {
    498      if (meanDuration.empty()) {
    499        // No whole frames available, use aTime as end time.
    500        framerate = Some(1.0f / (aTime - mStartTime).ToSeconds());
    501      } else {
    502        // We want some frames for estimating the framerate.
    503        framerate = Some(1.0f / meanDuration.mean().ToSeconds());
    504      }
    505    } else if ((aTime - mStartTime).ToSeconds() >
    506               FRAMERATE_DETECTION_MAX_DURATION_S) {
    507      // Instead of failing init after the fail-timeout, we fallback to a very
    508      // low rate.
    509      framerate = Some(static_cast<float>(frameCount) /
    510                       (aTime - mStartTime).ToSeconds());
    511    }
    512  }
    513 
    514  if (framerate) {
    515    for (VideoSegment::ConstChunkIterator iter(aSegment); !iter.IsEnded();
    516         iter.Next()) {
    517      if (iter->IsNull()) {
    518        continue;
    519      }
    520 
    521      gfx::IntSize imgsize = iter->mFrame.GetImage()->GetSize();
    522      gfx::IntSize intrinsicSize = iter->mFrame.GetIntrinsicSize();
    523      nsresult rv = Init(imgsize.width, imgsize.height, intrinsicSize.width,
    524                         intrinsicSize.height, *framerate);
    525 
    526      if (NS_SUCCEEDED(rv)) {
    527        TRACK_LOG(LogLevel::Info,
    528                  ("[VideoTrackEncoder %p]: Successfully initialized!", this));
    529        return;
    530      }
    531 
    532      TRACK_LOG(
    533          LogLevel::Error,
    534          ("[VideoTrackEncoder %p]: Failed to initialize the encoder!", this));
    535      OnError();
    536      break;
    537    }
    538  }
    539 
    540  if (((aTime - mStartTime).ToSeconds() > VIDEO_INIT_FAILED_DURATION) &&
    541      mInitCounter > 1) {
    542    TRACK_LOG(LogLevel::Warning,
    543              ("[VideoTrackEncoder %p]: No successful init for %ds.", this,
    544               VIDEO_INIT_FAILED_DURATION));
    545    OnError();
    546    return;
    547  }
    548 }
    549 
    550 void VideoTrackEncoder::Cancel() {
    551  MOZ_ASSERT(!mWorkerThread || mWorkerThread->IsCurrentThreadIn());
    552  TRACK_LOG(LogLevel::Info, ("[VideoTrackEncoder %p]: Cancel()", this));
    553  mCanceled = true;
    554  mEndOfStream = true;
    555  mIncomingBuffer.Clear();
    556  mOutgoingBuffer.Clear();
    557  mLastChunk.SetNull(0);
    558  mEncodedDataQueue.Finish();
    559 }
    560 
    561 void VideoTrackEncoder::NotifyEndOfStream() {
    562  MOZ_ASSERT(!mWorkerThread || mWorkerThread->IsCurrentThreadIn());
    563 
    564  if (mCanceled) {
    565    return;
    566  }
    567 
    568  if (mEndOfStream) {
    569    // We have already been notified.
    570    return;
    571  }
    572 
    573  mEndOfStream = true;
    574  TRACK_LOG(LogLevel::Info,
    575            ("[VideoTrackEncoder %p]: NotifyEndOfStream()", this));
    576 
    577  if (!mLastChunk.IsNull()) {
    578    RefPtr<layers::Image> lastImage = mLastChunk.mFrame.GetImage();
    579    const TimeStamp now = TimeStamp::Now();
    580    TimeStamp currentTime = mSuspended ? mSuspendTime : mCurrentTime;
    581    currentTime = mDriftCompensator->GetVideoTime(now, currentTime);
    582    TimeDuration absoluteEndTime = currentTime - mStartTime;
    583    CheckedInt64 duration =
    584        UsecsToFrames(absoluteEndTime.ToMicroseconds(), mTrackRate) -
    585        mEncodedTicks;
    586    if (duration.isValid() && duration.value() > 0) {
    587      mEncodedTicks += duration.value();
    588      TRACK_LOG(LogLevel::Debug,
    589                ("[VideoTrackEncoder %p]: Appending last video frame %p at pos "
    590                 "%.3fs, "
    591                 "track-end=%.3fs",
    592                 this, lastImage.get(),
    593                 (mLastChunk.mTimeStamp - mStartTime).ToSeconds(),
    594                 absoluteEndTime.ToSeconds()));
    595      mOutgoingBuffer.AppendFrame(
    596          lastImage.forget(), mLastChunk.mFrame.GetIntrinsicSize(),
    597          PRINCIPAL_HANDLE_NONE, mLastChunk.mFrame.GetForceBlack() || !mEnabled,
    598          mLastChunk.mTimeStamp);
    599      mOutgoingBuffer.ExtendLastFrameBy(duration.value());
    600    }
    601 
    602    if (!mInitialized) {
    603      // Try to init without waiting for an accurate framerate.
    604      Init(mOutgoingBuffer, currentTime, 0);
    605    }
    606  }
    607 
    608  if (mCanceled) {
    609    // Previous Init failed and we got canceled. Nothing to do here.
    610    return;
    611  }
    612 
    613  mIncomingBuffer.Clear();
    614  mLastChunk.SetNull(0);
    615 
    616  if (NS_WARN_IF(!mInitialized)) {
    617    // Still not initialized. There was probably no real frame at all, perhaps
    618    // by muting. Initialize the encoder with default frame width, frame
    619    // height, and frame rate.
    620    Init(DEFAULT_FRAME_WIDTH, DEFAULT_FRAME_HEIGHT, DEFAULT_FRAME_WIDTH,
    621         DEFAULT_FRAME_HEIGHT, DEFAULT_FRAME_RATE);
    622  }
    623 
    624  if (NS_FAILED(Encode(&mOutgoingBuffer))) {
    625    OnError();
    626  }
    627 
    628  MOZ_ASSERT(mOutgoingBuffer.IsEmpty());
    629 }
    630 
    631 void VideoTrackEncoder::SetStartOffset(const TimeStamp& aStartOffset) {
    632  MOZ_ASSERT(!mWorkerThread || mWorkerThread->IsCurrentThreadIn());
    633  MOZ_ASSERT(mCurrentTime.IsNull());
    634  TRACK_LOG(LogLevel::Info, ("[VideoTrackEncoder %p]: SetStartOffset()", this));
    635  mStartTime = aStartOffset;
    636  mCurrentTime = aStartOffset;
    637 }
    638 
    639 void VideoTrackEncoder::AdvanceCurrentTime(const TimeStamp& aTime) {
    640  AUTO_PROFILER_LABEL("VideoTrackEncoder::AdvanceCurrentTime", OTHER);
    641 
    642  MOZ_ASSERT(!mWorkerThread || mWorkerThread->IsCurrentThreadIn());
    643  MOZ_ASSERT(!mStartTime.IsNull());
    644  MOZ_ASSERT(!mCurrentTime.IsNull());
    645 
    646  if (mCanceled) {
    647    return;
    648  }
    649 
    650  if (mEndOfStream) {
    651    return;
    652  }
    653 
    654  if (mSuspended) {
    655    TRACK_LOG(
    656        LogLevel::Verbose,
    657        ("[VideoTrackEncoder %p]: AdvanceCurrentTime() suspended at %.3fs",
    658         this, (mCurrentTime - mStartTime).ToSeconds()));
    659    mCurrentTime = aTime;
    660    mIncomingBuffer.ForgetUpToTime(mCurrentTime);
    661    return;
    662  }
    663 
    664  TRACK_LOG(LogLevel::Verbose,
    665            ("[VideoTrackEncoder %p]: AdvanceCurrentTime() to %.3fs", this,
    666             (aTime - mStartTime).ToSeconds()));
    667 
    668  // Grab frames within the currentTime range from the incoming buffer.
    669  VideoSegment tempSegment;
    670  {
    671    VideoChunk* previousChunk = &mLastChunk;
    672    auto appendDupes = [&](const TimeStamp& aUpTo) {
    673      while ((aUpTo - previousChunk->mTimeStamp).ToSeconds() > 1.0) {
    674        // We encode at least one frame per second, even if there are none
    675        // flowing.
    676        previousChunk->mTimeStamp += TimeDuration::FromSeconds(1.0);
    677        tempSegment.AppendFrame(
    678            do_AddRef(previousChunk->mFrame.GetImage()),
    679            previousChunk->mFrame.GetIntrinsicSize(),
    680            previousChunk->mFrame.GetPrincipalHandle(),
    681            previousChunk->mFrame.GetForceBlack() || !mEnabled,
    682            previousChunk->mTimeStamp);
    683        TRACK_LOG(
    684            LogLevel::Verbose,
    685            ("[VideoTrackEncoder %p]: Duplicating video frame (%p) at pos %.3f",
    686             this, previousChunk->mFrame.GetImage(),
    687             (previousChunk->mTimeStamp - mStartTime).ToSeconds()));
    688      }
    689    };
    690    for (VideoSegment::ChunkIterator iter(mIncomingBuffer); !iter.IsEnded();
    691         iter.Next()) {
    692      MOZ_ASSERT(!iter->IsNull());
    693      if (!previousChunk->IsNull() &&
    694          iter->mTimeStamp <= previousChunk->mTimeStamp) {
    695        // This frame starts earlier than previousChunk. Skip.
    696        continue;
    697      }
    698      if (iter->mTimeStamp >= aTime) {
    699        // This frame starts in the future. Stop.
    700        break;
    701      }
    702      if (!previousChunk->IsNull()) {
    703        appendDupes(iter->mTimeStamp);
    704      }
    705      tempSegment.AppendFrame(
    706          do_AddRef(iter->mFrame.GetImage()), iter->mFrame.GetIntrinsicSize(),
    707          iter->mFrame.GetPrincipalHandle(),
    708          iter->mFrame.GetForceBlack() || !mEnabled, iter->mTimeStamp);
    709      TRACK_LOG(LogLevel::Verbose,
    710                ("[VideoTrackEncoder %p]: Taking video frame (%p) at pos %.3f",
    711                 this, iter->mFrame.GetImage(),
    712                 (iter->mTimeStamp - mStartTime).ToSeconds()));
    713      previousChunk = &*iter;
    714    }
    715    if (!previousChunk->IsNull()) {
    716      appendDupes(aTime);
    717    }
    718  }
    719  mCurrentTime = aTime;
    720  mIncomingBuffer.ForgetUpToTime(mCurrentTime);
    721 
    722  // Convert tempSegment timestamps to durations and add chunks with known
    723  // duration to mOutgoingBuffer.
    724  const TimeStamp now = TimeStamp::Now();
    725  for (VideoSegment::ConstChunkIterator iter(tempSegment); !iter.IsEnded();
    726       iter.Next()) {
    727    VideoChunk chunk = *iter;
    728 
    729    if (mLastChunk.mTimeStamp.IsNull()) {
    730      // This is the first real chunk in the track. Make it start at the
    731      // beginning of the track.
    732      MOZ_ASSERT(!iter->mTimeStamp.IsNull());
    733 
    734      TRACK_LOG(
    735          LogLevel::Verbose,
    736          ("[VideoTrackEncoder %p]: Got the first video frame (%p) at pos %.3f "
    737           "(moving it to beginning)",
    738           this, iter->mFrame.GetImage(),
    739           (iter->mTimeStamp - mStartTime).ToSeconds()));
    740 
    741      mLastChunk = *iter;
    742      mLastChunk.mTimeStamp = mStartTime;
    743      continue;
    744    }
    745 
    746    MOZ_ASSERT(!mLastChunk.IsNull());
    747    MOZ_ASSERT(!chunk.IsNull());
    748 
    749    TimeDuration absoluteEndTime =
    750        mDriftCompensator->GetVideoTime(now, chunk.mTimeStamp) - mStartTime;
    751    TRACK_LOG(LogLevel::Verbose,
    752              ("[VideoTrackEncoder %p]: Appending video frame %p, at pos %.3fs "
    753               "until %.3fs",
    754               this, mLastChunk.mFrame.GetImage(),
    755               (mDriftCompensator->GetVideoTime(now, mLastChunk.mTimeStamp) -
    756                mStartTime)
    757                   .ToSeconds(),
    758               absoluteEndTime.ToSeconds()));
    759    CheckedInt64 duration =
    760        UsecsToFrames(absoluteEndTime.ToMicroseconds(), mTrackRate) -
    761        mEncodedTicks;
    762    if (!duration.isValid()) {
    763      NS_ERROR("Duration overflow");
    764      return;
    765    }
    766 
    767    if (duration.value() <= 0) {
    768      // A frame either started before the last frame (can happen when
    769      // multiple frames are added before SetStartOffset), or
    770      // two frames were so close together that they ended up at the same
    771      // position. We handle both cases by ignoring the previous frame.
    772 
    773      TRACK_LOG(LogLevel::Verbose,
    774                ("[VideoTrackEncoder %p]: Duration from frame %p to frame %p "
    775                 "is %" PRId64 ". Ignoring %p",
    776                 this, mLastChunk.mFrame.GetImage(), iter->mFrame.GetImage(),
    777                 duration.value(), mLastChunk.mFrame.GetImage()));
    778 
    779      TimeStamp t = mLastChunk.mTimeStamp;
    780      mLastChunk = *iter;
    781      mLastChunk.mTimeStamp = t;
    782      continue;
    783    }
    784 
    785    mEncodedTicks += duration.value();
    786    mOutgoingBuffer.AppendFrame(
    787        do_AddRef(mLastChunk.mFrame.GetImage()),
    788        mLastChunk.mFrame.GetIntrinsicSize(), PRINCIPAL_HANDLE_NONE,
    789        mLastChunk.mFrame.GetForceBlack() || !mEnabled, mLastChunk.mTimeStamp);
    790    mOutgoingBuffer.ExtendLastFrameBy(duration.value());
    791    mLastChunk = chunk;
    792  }
    793 
    794  if (mOutgoingBuffer.IsEmpty()) {
    795    return;
    796  }
    797 
    798  Init(mOutgoingBuffer, mCurrentTime, FRAMERATE_DETECTION_MIN_CHUNKS);
    799 
    800  if (!mInitialized) {
    801    return;
    802  }
    803 
    804  if (NS_FAILED(Encode(&mOutgoingBuffer))) {
    805    OnError();
    806    return;
    807  }
    808 
    809  MOZ_ASSERT(mOutgoingBuffer.IsEmpty());
    810 }
    811 
    812 size_t VideoTrackEncoder::SizeOfExcludingThis(
    813    mozilla::MallocSizeOf aMallocSizeOf) {
    814  MOZ_ASSERT(!mWorkerThread || mWorkerThread->IsCurrentThreadIn());
    815  return mIncomingBuffer.SizeOfExcludingThis(aMallocSizeOf) +
    816         mOutgoingBuffer.SizeOfExcludingThis(aMallocSizeOf);
    817 }
    818 
    819 }  // namespace mozilla
    820 
    821 #undef TRACK_LOG