tor-browser

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

WMFMediaDataDecoder.cpp (10429B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
      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 http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "WMFMediaDataDecoder.h"
      8 
      9 #include "VideoUtils.h"
     10 #include "WMFUtils.h"
     11 #include "mozilla/Logging.h"
     12 #include "mozilla/ProfilerMarkers.h"
     13 #include "mozilla/SyncRunnable.h"
     14 #include "mozilla/TaskQueue.h"
     15 #include "nsTArray.h"
     16 
     17 #define LOG(...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
     18 
     19 namespace mozilla {
     20 
     21 WMFMediaDataDecoder::WMFMediaDataDecoder(MFTManager* aMFTManager)
     22    : mTaskQueue(TaskQueue::Create(
     23          GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER),
     24          "WMFMediaDataDecoder")),
     25      mMFTManager(aMFTManager) {}
     26 
     27 WMFMediaDataDecoder::~WMFMediaDataDecoder() {}
     28 
     29 RefPtr<MediaDataDecoder::InitPromise> WMFMediaDataDecoder::Init() {
     30  MOZ_ASSERT(!mIsShutDown);
     31  return InitPromise::CreateAndResolve(mMFTManager->GetType(), __func__);
     32 }
     33 
     34 RefPtr<ShutdownPromise> WMFMediaDataDecoder::Shutdown() {
     35  MOZ_DIAGNOSTIC_ASSERT(!mIsShutDown);
     36  mIsShutDown = true;
     37 
     38  return InvokeAsync(mTaskQueue, __func__, [self = RefPtr{this}, this] {
     39    AUTO_PROFILER_LABEL("WMFMediaDataDecoder::Shutdown", MEDIA_PLAYBACK);
     40    if (mMFTManager) {
     41      mMFTManager->Shutdown();
     42      mMFTManager = nullptr;
     43    }
     44    return mTaskQueue->BeginShutdown();
     45  });
     46 }
     47 
     48 // Inserts data into the decoder's pipeline.
     49 RefPtr<MediaDataDecoder::DecodePromise> WMFMediaDataDecoder::Decode(
     50    MediaRawData* aSample) {
     51  MOZ_DIAGNOSTIC_ASSERT(!mIsShutDown);
     52 
     53  return InvokeAsync<MediaRawData*>(
     54      mTaskQueue, this, __func__, &WMFMediaDataDecoder::ProcessDecode, aSample);
     55 }
     56 
     57 RefPtr<MediaDataDecoder::DecodePromise> WMFMediaDataDecoder::ProcessError(
     58    HRESULT aError, const char* aReason) {
     59  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
     60 
     61  nsPrintfCString markerString(
     62      "WMFMediaDataDecoder::ProcessError for decoder with description %s with "
     63      "reason: %s",
     64      GetDescriptionName().get(), aReason);
     65  LOG("%s", markerString.get());
     66  PROFILER_MARKER_TEXT("WMFDecoder Error", MEDIA_PLAYBACK, {}, markerString);
     67 
     68  // TODO: For the error DXGI_ERROR_DEVICE_RESET, we could return
     69  // NS_ERROR_DOM_MEDIA_NEED_NEW_DECODER to get the latest device. Maybe retry
     70  // up to 3 times.
     71  return DecodePromise::CreateAndReject(
     72      MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
     73                  RESULT_DETAIL("%s:%lx", aReason, aError)),
     74      __func__);
     75 }
     76 
     77 RefPtr<MediaDataDecoder::DecodePromise> WMFMediaDataDecoder::ProcessDecode(
     78    MediaRawData* aSample) {
     79  AUTO_PROFILER_LABEL("WMFMediaDataDecoder::ProcessDecode", MEDIA_PLAYBACK);
     80  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
     81  DecodedData results;
     82  LOG("ProcessDecode, type=%s, sample=%" PRId64 ", duration=%" PRId64,
     83      TrackTypeToStr(mMFTManager->GetType()), aSample->mTime.ToMicroseconds(),
     84      aSample->mDuration.ToMicroseconds());
     85  HRESULT hr = mMFTManager->Input(aSample);
     86  if (hr == MF_E_NOTACCEPTING) {
     87    hr = ProcessOutput(results);
     88    if (FAILED(hr) && hr != MF_E_TRANSFORM_NEED_MORE_INPUT) {
     89      return ProcessError(hr, "MFTManager::Output(1)");
     90    }
     91    hr = mMFTManager->Input(aSample);
     92  }
     93 
     94  if (FAILED(hr)) {
     95    NS_WARNING("MFTManager rejected sample");
     96    return ProcessError(hr, "MFTManager::Input");
     97  }
     98 
     99  if (mOutputsCount == 0) {
    100    mInputTimesSet.insert(aSample->mTime.ToMicroseconds());
    101  }
    102 
    103  if (!mLastTime || aSample->mTime > *mLastTime) {
    104    mLastTime = Some(aSample->mTime);
    105    mLastDuration = aSample->mDuration;
    106  }
    107 
    108  mSamplesCount++;
    109  mDrainStatus = DrainStatus::DRAINABLE;
    110  mLastStreamOffset = aSample->mOffset;
    111 
    112  hr = ProcessOutput(results);
    113  if (SUCCEEDED(hr) || hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
    114    return DecodePromise::CreateAndResolve(std::move(results), __func__);
    115  }
    116  return ProcessError(hr, "MFTManager::Output(2)");
    117 }
    118 
    119 bool WMFMediaDataDecoder::ShouldGuardAgaintIncorrectFirstSample(
    120    MediaData* aOutput) const {
    121  // Incorrect first samples have only been observed in video tracks, so only
    122  // guard video tracks.
    123  if (mMFTManager->GetType() != TrackInfo::kVideoTrack) {
    124    return false;
    125  }
    126 
    127  // This is not the first output sample so we don't need to guard it.
    128  if (mOutputsCount != 0) {
    129    return false;
    130  }
    131 
    132  // Output isn't in the map which contains the inputs we gave to the decoder.
    133  // This is probably the invalid first sample. MFT decoder sometime will return
    134  // incorrect first output to us, which always has 0 timestamp, even if the
    135  // input we gave to MFT has timestamp that is way later than 0.
    136  MOZ_ASSERT(!mInputTimesSet.empty());
    137  return mInputTimesSet.find(aOutput->mTime.ToMicroseconds()) ==
    138             mInputTimesSet.end() &&
    139         aOutput->mTime.ToMicroseconds() == 0;
    140 }
    141 
    142 HRESULT
    143 WMFMediaDataDecoder::ProcessOutput(DecodedData& aResults) {
    144  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
    145  RefPtr<MediaData> output;
    146  HRESULT hr = S_OK;
    147  while (SUCCEEDED(hr = mMFTManager->Output(mLastStreamOffset, output))) {
    148    MOZ_ASSERT(output.get(), "Upon success, we must receive an output");
    149    if (ShouldGuardAgaintIncorrectFirstSample(output)) {
    150      LOG("Discarding sample with time %" PRId64
    151          " because of ShouldGuardAgaintIncorrectFirstSample check",
    152          output->mTime.ToMicroseconds());
    153      continue;
    154    }
    155    if (++mOutputsCount == 1) {
    156      // Got first valid sample, don't need to guard following sample anymore.
    157      mInputTimesSet.clear();
    158    }
    159    aResults.AppendElement(std::move(output));
    160    // If zero-copy video is enabled, multiple WMFVideoMFTManager::Output()
    161    // calls must be prevented within WMFMediaDataDecoder::ProcessOutput().
    162    if (mMFTManager->UseZeroCopyVideoFrame()) {
    163      break;
    164    }
    165    if (mDrainStatus == DrainStatus::DRAINING) {
    166      break;
    167    }
    168  }
    169  return hr;
    170 }
    171 
    172 RefPtr<MediaDataDecoder::FlushPromise> WMFMediaDataDecoder::ProcessFlush() {
    173  AUTO_PROFILER_LABEL("WMFMediaDataDecoder::ProcessFlush", MEDIA_PLAYBACK);
    174  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
    175  if (mMFTManager) {
    176    mMFTManager->Flush();
    177  }
    178  LOG("ProcessFlush, type=%s", TrackTypeToStr(mMFTManager->GetType()));
    179  mDrainStatus = DrainStatus::DRAINED;
    180  mSamplesCount = 0;
    181  mOutputsCount = 0;
    182  mLastTime.reset();
    183  mInputTimesSet.clear();
    184  return FlushPromise::CreateAndResolve(true, __func__);
    185 }
    186 
    187 RefPtr<MediaDataDecoder::FlushPromise> WMFMediaDataDecoder::Flush() {
    188  MOZ_DIAGNOSTIC_ASSERT(!mIsShutDown);
    189 
    190  return InvokeAsync(mTaskQueue, this, __func__,
    191                     &WMFMediaDataDecoder::ProcessFlush);
    192 }
    193 
    194 RefPtr<MediaDataDecoder::DecodePromise> WMFMediaDataDecoder::ProcessDrain() {
    195  AUTO_PROFILER_LABEL("WMFMediaDataDecoder::ProcessDrain", MEDIA_PLAYBACK);
    196  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
    197  if (!mMFTManager || mDrainStatus == DrainStatus::DRAINED) {
    198    return DecodePromise::CreateAndResolve(DecodedData(), __func__);
    199  }
    200 
    201  if (mDrainStatus != DrainStatus::DRAINING) {
    202    // Order the decoder to drain...
    203    mMFTManager->Drain();
    204    mDrainStatus = DrainStatus::DRAINING;
    205  }
    206 
    207  // Then extract all available output.
    208  DecodedData results;
    209  HRESULT hr = ProcessOutput(results);
    210  if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
    211    mDrainStatus = DrainStatus::DRAINED;
    212  }
    213  if (SUCCEEDED(hr) || hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
    214    if (results.Length() > 0 &&
    215        results.LastElement()->mType == MediaData::Type::VIDEO_DATA) {
    216      const RefPtr<MediaData>& data = results.LastElement();
    217      if (mSamplesCount == 1 && data->mTime == media::TimeUnit::Zero()) {
    218        // WMF is unable to calculate a duration if only a single sample
    219        // was parsed. Additionally, the pts always comes out at 0 under those
    220        // circumstances.
    221        // Seeing that we've only fed the decoder a single frame, the pts
    222        // and duration are known, it's of the last sample.
    223        data->mTime = *mLastTime;
    224      }
    225      if (data->mTime == *mLastTime) {
    226        // The WMF Video decoder is sometimes unable to provide a valid duration
    227        // on the last sample even if it has been first set through
    228        // SetSampleTime (appears to always happen on Windows 7). So we force
    229        // set the duration of the last sample as it was input.
    230        data->mDuration = mLastDuration;
    231      }
    232    } else if (results.Length() == 1 &&
    233               results.LastElement()->mType == MediaData::Type::AUDIO_DATA) {
    234      // When we drain the audio decoder and one frame was queued (such as with
    235      // AAC) the MFT will re-calculate the starting time rather than use the
    236      // value set on the IMF Sample.
    237      // This is normally an okay thing to do; however when dealing with poorly
    238      // muxed content that has incorrect start time, it could lead to broken
    239      // A/V sync. So we ensure that we use the compressed sample's time
    240      // instead. Additionally, this is what all other audio decoders are doing
    241      // anyway.
    242      MOZ_ASSERT(mLastTime,
    243                 "We must have attempted to decode at least one frame to get "
    244                 "one decoded output");
    245      results.LastElement()->As<AudioData>()->SetOriginalStartTime(*mLastTime);
    246    }
    247    return DecodePromise::CreateAndResolve(std::move(results), __func__);
    248  }
    249  return ProcessError(hr, "MFTManager::Output");
    250 }
    251 
    252 RefPtr<MediaDataDecoder::DecodePromise> WMFMediaDataDecoder::Drain() {
    253  MOZ_DIAGNOSTIC_ASSERT(!mIsShutDown);
    254 
    255  return InvokeAsync(mTaskQueue, this, __func__,
    256                     &WMFMediaDataDecoder::ProcessDrain);
    257 }
    258 
    259 bool WMFMediaDataDecoder::IsHardwareAccelerated(
    260    nsACString& aFailureReason) const {
    261  MOZ_ASSERT(!mIsShutDown);
    262 
    263  return mMFTManager && mMFTManager->IsHardwareAccelerated(aFailureReason);
    264 }
    265 
    266 void WMFMediaDataDecoder::SetSeekThreshold(const media::TimeUnit& aTime) {
    267  MOZ_DIAGNOSTIC_ASSERT(!mIsShutDown);
    268 
    269  RefPtr<WMFMediaDataDecoder> self = this;
    270  nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
    271      "WMFMediaDataDecoder::SetSeekThreshold", [self, aTime]() {
    272        MOZ_ASSERT(self->mTaskQueue->IsCurrentThreadIn());
    273        media::TimeUnit threshold = aTime;
    274        self->mMFTManager->SetSeekThreshold(threshold);
    275      });
    276  nsresult rv = mTaskQueue->Dispatch(runnable.forget());
    277  MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
    278  (void)rv;
    279 }
    280 
    281 }  // namespace mozilla