dom/media/fmp4/wmf/WMFMediaDataDecoder.cpp
author Karl Tomlinson <karlt+@karlt.net>
Wed, 29 Apr 2015 04:36:13 +1200
changeset 260512 825e8ac4ab29f84233d87c492fe7c973e0fea147
parent 260229 0920ace0d8b090fdaaf9f91de2ed9cb452b64439
child 267523 a2e16743fb0b99cca889aaf4c7e355f4436f8985
permissions -rw-r--r--
Bug 1159456 - Finish and exit from Flush() even if MFTManager rejects sample. r=cpearce, a=sledru

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "WMFMediaDataDecoder.h"
#include "VideoUtils.h"
#include "WMFUtils.h"
#include "nsTArray.h"

#include "prlog.h"

#ifdef PR_LOGGING
PRLogModuleInfo* GetDemuxerLog();
#define LOG(...) PR_LOG(GetDemuxerLog(), PR_LOG_DEBUG, (__VA_ARGS__))
#else
#define LOG(...)
#endif


namespace mozilla {

WMFMediaDataDecoder::WMFMediaDataDecoder(MFTManager* aMFTManager,
                                         FlushableMediaTaskQueue* aTaskQueue,
                                         MediaDataDecoderCallback* aCallback)
  : mTaskQueue(aTaskQueue)
  , mCallback(aCallback)
  , mMFTManager(aMFTManager)
  , mMonitor("WMFMediaDataDecoder")
  , mIsDecodeTaskDispatched(false)
  , mIsFlushing(false)
{
}

WMFMediaDataDecoder::~WMFMediaDataDecoder()
{
}

nsresult
WMFMediaDataDecoder::Init()
{
  mDecoder = mMFTManager->Init();
  NS_ENSURE_TRUE(mDecoder, NS_ERROR_FAILURE);

  return NS_OK;
}

nsresult
WMFMediaDataDecoder::Shutdown()
{
  DebugOnly<nsresult> rv = mTaskQueue->Dispatch(
    NS_NewRunnableMethod(this, &WMFMediaDataDecoder::ProcessShutdown));
#ifdef DEBUG
  if (NS_FAILED(rv)) {
    NS_WARNING("WMFMediaDataDecoder::Shutdown() dispatch of task failed!");
  }
  {
    MonitorAutoLock mon(mMonitor);
    // The MP4Reader should have flushed before calling Shutdown().
    MOZ_ASSERT(!mIsDecodeTaskDispatched);
  }
#endif
  return NS_OK;
}

void
WMFMediaDataDecoder::ProcessShutdown()
{
  if (mMFTManager) {
    mMFTManager->Shutdown();
    mMFTManager = nullptr;
  }
  mDecoder = nullptr;
}

void
WMFMediaDataDecoder::EnsureDecodeTaskDispatched()
{
  mMonitor.AssertCurrentThreadOwns();
  if (!mIsDecodeTaskDispatched) {
    mTaskQueue->Dispatch(
      NS_NewRunnableMethod(this,
      &WMFMediaDataDecoder::Decode));
    mIsDecodeTaskDispatched = true;
  }
}

void
WMFMediaDataDecoder::ProcessReleaseDecoder()
{
  mMFTManager->Shutdown();
  mDecoder = nullptr;
}

// Inserts data into the decoder's pipeline.
nsresult
WMFMediaDataDecoder::Input(mp4_demuxer::MP4Sample* aSample)
{
  MonitorAutoLock mon(mMonitor);
  mInput.push(nsAutoPtr<mp4_demuxer::MP4Sample>(aSample));
  EnsureDecodeTaskDispatched();
  return NS_OK;
}

void
WMFMediaDataDecoder::Decode()
{
  while (true) {
    nsAutoPtr<mp4_demuxer::MP4Sample> input;
    {
      MonitorAutoLock mon(mMonitor);
      MOZ_ASSERT(mIsDecodeTaskDispatched);
      if (mInput.empty()) {
        if (mIsFlushing) {
          if (mDecoder) {
            mDecoder->Flush();
          }
          mIsFlushing = false;
        }
        mIsDecodeTaskDispatched = false;
        mon.NotifyAll();
        return;
      }
      input = mInput.front();
      mInput.pop();
    }

    HRESULT hr = mMFTManager->Input(input);
    if (FAILED(hr)) {
      NS_WARNING("MFTManager rejected sample");
      {
        MonitorAutoLock mon(mMonitor);
        PurgeInputQueue();
      }
      mCallback->Error();
      continue; // complete flush if flushing
    }

    mLastStreamOffset = input->byte_offset;

    ProcessOutput();
  }
}

void
WMFMediaDataDecoder::ProcessOutput()
{
  nsRefPtr<MediaData> output;
  HRESULT hr = S_OK;
  while (SUCCEEDED(hr = mMFTManager->Output(mLastStreamOffset, output)) &&
         output) {
    mCallback->Output(output);
  }
  if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
    if (mTaskQueue->IsEmpty()) {
      mCallback->InputExhausted();
    }
  } else if (FAILED(hr)) {
    NS_WARNING("WMFMediaDataDecoder failed to output data");
    {
      MonitorAutoLock mon(mMonitor);
      PurgeInputQueue();
    }
    mCallback->Error();
  }
}

void
WMFMediaDataDecoder::PurgeInputQueue()
{
  mMonitor.AssertCurrentThreadOwns();
  while (!mInput.empty()) {
    mInput.pop();
  }
}

nsresult
WMFMediaDataDecoder::Flush()
{
  MonitorAutoLock mon(mMonitor);
  PurgeInputQueue();
  mIsFlushing = true;
  EnsureDecodeTaskDispatched();
  while (mIsDecodeTaskDispatched || mIsFlushing) {
    mon.Wait();
  }
  return NS_OK;
}

void
WMFMediaDataDecoder::ProcessDrain()
{
  if (mDecoder) {
    // Order the decoder to drain...
    if (FAILED(mDecoder->SendMFTMessage(MFT_MESSAGE_COMMAND_DRAIN, 0))) {
      NS_WARNING("Failed to send DRAIN command to MFT");
    }
    // Then extract all available output.
    ProcessOutput();
  }
  mCallback->DrainComplete();
}

nsresult
WMFMediaDataDecoder::Drain()
{
  mTaskQueue->Dispatch(NS_NewRunnableMethod(this, &WMFMediaDataDecoder::ProcessDrain));
  return NS_OK;
}

void
WMFMediaDataDecoder::AllocateMediaResources()
{
  mDecoder = mMFTManager->Init();
}

void
WMFMediaDataDecoder::ReleaseMediaResources()
{
  DebugOnly<nsresult> rv = mTaskQueue->FlushAndDispatch(
    NS_NewRunnableMethod(this, &WMFMediaDataDecoder::ProcessReleaseDecoder));
#ifdef DEBUG
  if (NS_FAILED(rv)) {
    NS_WARNING("WMFMediaDataDecoder::ReleaseMediaResources() dispatch of task failed!");
  }
#endif
}

bool
WMFMediaDataDecoder::IsHardwareAccelerated() const {
  return mMFTManager && mMFTManager->IsHardwareAccelerated();
}

} // namespace mozilla