Bug 1075199 - Import WMF decoding code from cpearce's gmp-clearkey implementation - r=cpearce a=lmandel
authorEdwin Flores <edwin@mozilla.com>
Fri, 16 Jan 2015 10:37:54 +1300
changeset 250190 9910b5a6a99f
parent 250189 c4b5f9a4cc0a
child 250191 6cb6bddb9b9d
push id4521
push usercpearce@mozilla.com
push date2015-03-04 01:22 +0000
treeherdermozilla-beta@8abdbdecd2d6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce, lmandel
bugs1075199
milestone37.0
Bug 1075199 - Import WMF decoding code from cpearce's gmp-clearkey implementation - r=cpearce a=lmandel
media/gmp-clearkey/0.1/AudioDecoder.cpp
media/gmp-clearkey/0.1/AudioDecoder.h
media/gmp-clearkey/0.1/VideoDecoder.cpp
media/gmp-clearkey/0.1/VideoDecoder.h
media/gmp-clearkey/0.1/WMFAACDecoder.cpp
media/gmp-clearkey/0.1/WMFAACDecoder.h
media/gmp-clearkey/0.1/WMFH264Decoder.cpp
media/gmp-clearkey/0.1/WMFH264Decoder.h
media/gmp-clearkey/0.1/WMFUtils.cpp
media/gmp-clearkey/0.1/WMFUtils.h
media/gmp-clearkey/0.1/moz.build
new file mode 100644
--- /dev/null
+++ b/media/gmp-clearkey/0.1/AudioDecoder.cpp
@@ -0,0 +1,256 @@
+/*
+ * Copyright 2013, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "stdafx.h"
+
+#ifdef TEST_DECODING
+
+AudioDecoder::AudioDecoder(GMPAudioHost *aHostAPI)
+  : mHostAPI(aHostAPI)
+  , mCallback(nullptr)
+  , mWorkerThread(nullptr)
+  , mMutex(nullptr)
+  , mNumInputTasks(0)
+{
+}
+
+AudioDecoder::~AudioDecoder()
+{
+  mMutex->Destroy();
+}
+
+void
+AudioDecoder::InitDecode(const GMPAudioCodec& aConfig,
+                         GMPAudioDecoderCallback* aCallback)
+{
+  mCallback = aCallback;
+  assert(mCallback);
+  mDecoder = new WMFAACDecoder();
+  HRESULT hr = mDecoder->Init(aConfig.mChannelCount,
+                              aConfig.mSamplesPerSecond,
+                              (BYTE*)aConfig.mExtraData,
+                              aConfig.mExtraDataLen);
+  LOG(L"[%p] WMFDecodingModule::InitializeAudioDecoder() hr=0x%x\n", this, hr);
+  if (FAILED(hr)) {
+    mCallback->Error(GMPGenericErr);
+    return;
+  }
+  auto err = GMPCreateMutex(&mMutex);
+  if (GMP_FAILED(err)) {
+    mCallback->Error(GMPGenericErr);
+    return;
+  }
+}
+
+void
+AudioDecoder::Decode(GMPAudioSamples* aInput)
+{
+  if (!mWorkerThread) {
+    GMPCreateThread(&mWorkerThread);
+    if (!mWorkerThread) {
+      mCallback->Error(GMPAllocErr);
+      return;
+    }
+  }
+  {
+    AutoLock lock(mMutex);
+    mNumInputTasks++;
+  }
+  mWorkerThread->Post(WrapTask(this,
+                               &AudioDecoder::DecodeTask,
+                               aInput));
+}
+
+void
+AudioDecoder::DecodeTask(GMPAudioSamples* aInput)
+{
+  HRESULT hr;
+
+  {
+    AutoLock lock(mMutex);
+    mNumInputTasks--;
+    assert(mNumInputTasks >= 0);
+  }
+
+  if (!aInput || !mHostAPI || !mDecoder) {
+    LOG(L"Decode job not set up correctly!");
+    return;
+  }
+
+  const uint8_t* inBuffer = aInput->Buffer();
+  if (!inBuffer) {
+    LOG(L"No buffer for encoded samples!\n");
+    return;
+  }
+
+  const GMPEncryptedBufferMetadata* crypto = aInput->GetDecryptionData();
+  std::vector<uint8_t> buffer;
+  if (crypto) {
+    const uint8_t* iv = crypto->IV();
+    uint32_t sz = crypto->IVSize();
+    // Plugin host should have set up its decryptor/key sessions
+    // before trying to decode!
+    auto decryptor = Decryptor::Get(crypto);
+    if (!decryptor ||
+        !decryptor->Decrypt(inBuffer, aInput->Size(), crypto, buffer)) {
+      LOG(L"Audio decryption error!");
+      mCallback->Error(GMPNoKeyErr);
+      return;
+    }
+    inBuffer = buffer.data();
+    assert(buffer.size() == aInput->Size());
+  }
+
+  hr = mDecoder->Input(inBuffer,
+                       aInput->Size(),
+                       aInput->TimeStamp());
+
+  // We must delete the input sample!
+  GMPRunOnMainThread(WrapTask(aInput, &GMPAudioSamples::Destroy));
+
+  SAMPLE_LOG(L"AudioDecoder::DecodeTask() Input ret hr=0x%x\n", hr);
+  if (FAILED(hr)) {
+    LOG(L"AudioDecoder::DecodeTask() decode failed ret=0x%x%s\n",
+        hr,
+        ((hr == MF_E_NOTACCEPTING) ? " (MF_E_NOTACCEPTING)" : ""));
+    return;
+  }
+
+  while (hr == S_OK) {
+    CComPtr<IMFSample> output;
+    hr = mDecoder->Output(&output);
+    SAMPLE_LOG(L"AudioDecoder::DecodeTask() output ret=0x%x\n", hr);
+    if (hr == S_OK) {
+      ReturnOutput(output);
+    }
+    if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
+      AutoLock lock(mMutex);
+      if (mNumInputTasks == 0) {
+        // We have run all input tasks. We *must* notify Gecko so that it will
+        // send us more data.
+        GMPRunOnMainThread(WrapTask(mCallback, &GMPAudioDecoderCallback::InputDataExhausted));
+      }
+    } else if (FAILED(hr)) {
+      LOG(L"AudioDecoder::DecodeTask() output failed hr=0x%x\n", hr);
+    }
+  }
+}
+
+void
+AudioDecoder::ReturnOutput(IMFSample* aSample)
+{
+  SAMPLE_LOG(L"[%p] WMFDecodingModule::OutputVideoFrame()\n", this);
+  assert(aSample);
+
+  HRESULT hr;
+
+  GMPAudioSamples* samples = nullptr;
+  mHostAPI->CreateSamples(kGMPAudioIS16Samples, &samples);
+  if (!samples) {
+    LOG(L"Failed to create i420 frame!\n");
+    return;
+  }
+
+  hr = MFToGMPSample(aSample, samples);
+  if (FAILED(hr)) {
+    samples->Destroy();
+    LOG(L"Failed to prepare output sample!");
+    return;
+  }
+  ENSURE(SUCCEEDED(hr), /*void*/);
+
+  GMPRunOnMainThread(WrapTask(mCallback, &GMPAudioDecoderCallback::Decoded, samples));
+}
+
+HRESULT
+AudioDecoder::MFToGMPSample(IMFSample* aInput,
+                            GMPAudioSamples* aOutput)
+{
+  ENSURE(aInput != nullptr, E_POINTER);
+  ENSURE(aOutput != nullptr, E_POINTER);
+
+  HRESULT hr;
+  CComPtr<IMFMediaBuffer> mediaBuffer;
+
+  hr = aInput->ConvertToContiguousBuffer(&mediaBuffer);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  BYTE* data = nullptr; // Note: *data will be owned by the IMFMediaBuffer, we don't need to free it.
+  DWORD maxLength = 0, currentLength = 0;
+  hr = mediaBuffer->Lock(&data, &maxLength, &currentLength);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  auto err = aOutput->SetBufferSize(currentLength);
+  ENSURE(GMP_SUCCEEDED(err), E_FAIL);
+
+  memcpy(aOutput->Buffer(), data, currentLength);
+
+  mediaBuffer->Unlock();
+
+  LONGLONG hns = 0;
+  hr = aInput->GetSampleTime(&hns);
+  ENSURE(SUCCEEDED(hr), hr);
+  aOutput->SetTimeStamp(HNsToUsecs(hns));
+  aOutput->SetChannels(mDecoder->Channels());
+  aOutput->SetRate(mDecoder->Rate());
+
+  return S_OK;
+}
+
+void
+AudioDecoder::Reset()
+{
+  mDecoder->Reset();
+  mCallback->ResetComplete();
+}
+
+void
+AudioDecoder::DrainTask()
+{
+  if (FAILED(mDecoder->Drain())) {
+    GMPSyncRunOnMainThread(WrapTask(mCallback, &GMPAudioDecoderCallback::DrainComplete));
+  }
+
+  // Return any pending output.
+  HRESULT hr = S_OK;
+  while (hr == S_OK) {
+    CComPtr<IMFSample> output;
+    hr = mDecoder->Output(&output);
+    SAMPLE_LOG(L"AudioDecoder::DrainTask() output ret=0x%x\n", hr);
+    if (hr == S_OK) {
+      ReturnOutput(output);
+    }
+  }
+  GMPSyncRunOnMainThread(WrapTask(mCallback, &GMPAudioDecoderCallback::DrainComplete));
+}
+
+void
+AudioDecoder::Drain()
+{
+  mWorkerThread->Post(WrapTask(this,
+                               &AudioDecoder::DrainTask));
+}
+
+void
+AudioDecoder::DecodingComplete()
+{
+  if (mWorkerThread) {
+    mWorkerThread->Join();
+  }
+  delete this;
+}
+
+#endif
new file mode 100644
--- /dev/null
+++ b/media/gmp-clearkey/0.1/AudioDecoder.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2013, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "stdafx.h"
+
+#ifdef TEST_DECODING
+
+class AudioDecoder : public GMPAudioDecoder
+{
+public:
+  AudioDecoder(GMPAudioHost *aHostAPI);
+
+  virtual ~AudioDecoder();
+
+  virtual void InitDecode(const GMPAudioCodec& aCodecSettings,
+                          GMPAudioDecoderCallback* aCallback) override;
+
+  virtual void Decode(GMPAudioSamples* aEncodedSamples);
+
+  virtual void Reset() override;
+
+  virtual void Drain() override;
+
+  virtual void DecodingComplete() override;
+
+private:
+
+  void DecodeTask(GMPAudioSamples* aEncodedSamples);
+  void DrainTask();
+
+  void ReturnOutput(IMFSample* aSample);
+
+  HRESULT MFToGMPSample(IMFSample* aSample,
+                        GMPAudioSamples* aAudioFrame);
+
+  GMPAudioHost *mHostAPI; // host-owned, invalid at DecodingComplete
+  GMPAudioDecoderCallback* mCallback; // host-owned, invalid at DecodingComplete
+  GMPThread* mWorkerThread;
+  GMPMutex* mMutex;
+  AutoPtr<WMFAACDecoder> mDecoder;
+
+  int32_t mNumInputTasks;
+};
+
+#endif // TEST_DECODING
new file mode 100644
--- /dev/null
+++ b/media/gmp-clearkey/0.1/VideoDecoder.cpp
@@ -0,0 +1,345 @@
+/*
+ * Copyright 2013, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "stdafx.h"
+
+#ifdef TEST_DECODING
+
+VideoDecoder::VideoDecoder(GMPVideoHost *aHostAPI)
+  : mHostAPI(aHostAPI)
+  , mCallback(nullptr)
+  , mWorkerThread(nullptr)
+  , mMutex(nullptr)
+  , mNumInputTasks(0)
+  , mSentExtraData(false)
+{
+}
+
+VideoDecoder::~VideoDecoder()
+{
+  mMutex->Destroy();
+}
+
+void
+VideoDecoder::InitDecode(const GMPVideoCodec& aCodecSettings,
+                         const uint8_t* aCodecSpecific,
+                         uint32_t aCodecSpecificLength,
+                         GMPVideoDecoderCallback* aCallback,
+                         int32_t aCoreCount)
+{
+  mCallback = aCallback;
+  assert(mCallback);
+  mDecoder = new WMFH264Decoder();
+  HRESULT hr = mDecoder->Init();
+  if (FAILED(hr)) {
+    mCallback->Error(GMPGenericErr);
+    return;
+  }
+
+  auto err = GMPCreateMutex(&mMutex);
+  if (GMP_FAILED(err)) {
+    mCallback->Error(GMPGenericErr);
+    return;
+  }
+
+  // The first byte is mPacketizationMode, which is only relevant for
+  // WebRTC/OpenH264 usecase.
+  const uint8_t* avcc = aCodecSpecific + 1;
+  const uint8_t* avccEnd = aCodecSpecific + aCodecSpecificLength;
+  mExtraData.insert(mExtraData.end(), avcc, avccEnd);
+
+  if (!mAVCC.Parse(mExtraData) ||
+      !AVC::ConvertConfigToAnnexB(mAVCC, &mAnnexB)) {
+    mCallback->Error(GMPGenericErr);
+    return;
+  }
+}
+
+void
+VideoDecoder::Decode(GMPVideoEncodedFrame* aInputFrame,
+                     bool aMissingFrames,
+                     const uint8_t* aCodecSpecificInfo,
+                     uint32_t aCodecSpecificInfoLength,
+                     int64_t aRenderTimeMs)
+{
+  if (aInputFrame->BufferType() != GMP_BufferLength32) {
+    // Gecko should only send frames with 4 byte NAL sizes to GMPs.
+    mCallback->Error(GMPGenericErr);
+    return;
+  }
+
+  if (!mWorkerThread) {
+    GMPCreateThread(&mWorkerThread);
+    if (!mWorkerThread) {
+      mCallback->Error(GMPAllocErr);
+      return;
+    }
+  }
+  {
+    AutoLock lock(mMutex);
+    mNumInputTasks++;
+  }
+
+  // Note: we don't need the codec specific info on a per-frame basis.
+  // It's mostly useful for WebRTC use cases.
+
+  mWorkerThread->Post(WrapTask(this,
+                               &VideoDecoder::DecodeTask,
+                               aInputFrame));
+}
+
+static const uint8_t kAnnexBDelimiter[] = { 0, 0, 0, 1 };
+
+void
+VideoDecoder::DecodeTask(GMPVideoEncodedFrame* aInput)
+{
+  HRESULT hr;
+
+  {
+    AutoLock lock(mMutex);
+    mNumInputTasks--;
+    assert(mNumInputTasks >= 0);
+  }
+
+  if (!aInput || !mHostAPI || !mDecoder) {
+    LOG(L"Decode job not set up correctly!");
+    return;
+  }
+
+  const uint8_t* inBuffer = aInput->Buffer();
+  if (!inBuffer) {
+    LOG(L"No buffer for encoded frame!\n");
+    return;
+  }
+
+  const GMPEncryptedBufferMetadata* crypto = aInput->GetDecryptionData();
+  std::vector<uint8_t> buffer;
+  if (crypto) {
+    // Plugin host should have set up its decryptor/key sessions
+    // before trying to decode!
+    auto decryptor = Decryptor::Get(crypto);
+    if (!decryptor ||
+        !decryptor->Decrypt(inBuffer, aInput->Size(), crypto, buffer)) {
+      LOG(L"Video decryption error!");
+      mCallback->Error(GMPNoKeyErr);
+      return;
+    }
+    buffer.data();
+  } else {
+    buffer.insert(buffer.end(), inBuffer, inBuffer+aInput->Size());
+  }
+
+  AVC::ConvertFrameToAnnexB(4, &buffer);
+  if (aInput->FrameType() == kGMPKeyFrame) {
+    // We must send the SPS and PPS to Windows Media Foundation's decoder.
+    // Note: We do this *after* decryption, otherwise the subsample info
+    // would be incorrect.
+    buffer.insert(buffer.begin(), mAnnexB.begin(), mAnnexB.end());
+  }
+
+  hr = mDecoder->Input(buffer.data(),
+                       buffer.size(),
+                       aInput->TimeStamp(),
+                       aInput->Duration());
+
+  // We must delete the input sample!
+  GMPRunOnMainThread(WrapTask(aInput, &GMPVideoEncodedFrame::Destroy));
+
+  SAMPLE_LOG(L"VideoDecoder::DecodeTask() Input ret hr=0x%x\n", hr);
+  if (FAILED(hr)) {
+    LOG(L"VideoDecoder::DecodeTask() decode failed ret=0x%x%s\n",
+        hr,
+        ((hr == MF_E_NOTACCEPTING) ? " (MF_E_NOTACCEPTING)" : ""));
+    return;
+  }
+
+  while (hr == S_OK) {
+    CComPtr<IMFSample> output;
+    hr = mDecoder->Output(&output);
+    SAMPLE_LOG(L"VideoDecoder::DecodeTask() output ret=0x%x\n", hr);
+    if (hr == S_OK) {
+      ReturnOutput(output);
+    }
+    if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
+      AutoLock lock(mMutex);
+      if (mNumInputTasks == 0) {
+        // We have run all input tasks. We *must* notify Gecko so that it will
+        // send us more data.
+        GMPRunOnMainThread(WrapTask(mCallback, &GMPVideoDecoderCallback::InputDataExhausted));
+      }
+    }
+    if (FAILED(hr)) {
+      LOG(L"VideoDecoder::DecodeTask() output failed hr=0x%x\n", hr);
+    }
+  }
+}
+
+void
+VideoDecoder::ReturnOutput(IMFSample* aSample)
+{
+  SAMPLE_LOG(L"[%p] WMFDecodingModule::OutputVideoFrame()\n", this);
+  assert(aSample);
+
+  HRESULT hr;
+
+  GMPVideoFrame* f = nullptr;
+  mHostAPI->CreateFrame(kGMPI420VideoFrame, &f);
+  if (!f) {
+    LOG(L"Failed to create i420 frame!\n");
+    return;
+  }
+  auto vf = static_cast<GMPVideoi420Frame*>(f);
+
+  hr = SampleToVideoFrame(aSample, vf);
+  ENSURE(SUCCEEDED(hr), /*void*/);
+
+  GMPRunOnMainThread(WrapTask(mCallback, &GMPVideoDecoderCallback::Decoded, vf));
+
+}
+
+HRESULT
+VideoDecoder::SampleToVideoFrame(IMFSample* aSample,
+                                 GMPVideoi420Frame* aVideoFrame)
+{
+  ENSURE(aSample != nullptr, E_POINTER);
+  ENSURE(aVideoFrame != nullptr, E_POINTER);
+
+  HRESULT hr;
+  CComPtr<IMFMediaBuffer> mediaBuffer;
+
+  // Must convert to contiguous mediaBuffer to use IMD2DBuffer interface.
+  hr = aSample->ConvertToContiguousBuffer(&mediaBuffer);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  // Try and use the IMF2DBuffer interface if available, otherwise fallback
+  // to the IMFMediaBuffer interface. Apparently IMF2DBuffer is more efficient,
+  // but only some systems (Windows 8?) support it.
+  BYTE* data = nullptr;
+  LONG stride = 0;
+  CComPtr<IMF2DBuffer> twoDBuffer;
+  hr = mediaBuffer->QueryInterface(static_cast<IMF2DBuffer**>(&twoDBuffer));
+  if (SUCCEEDED(hr)) {
+    hr = twoDBuffer->Lock2D(&data, &stride);
+    ENSURE(SUCCEEDED(hr), hr);
+  } else {
+    hr = mediaBuffer->Lock(&data, NULL, NULL);
+    ENSURE(SUCCEEDED(hr), hr);
+    stride = mDecoder->GetStride();
+  }
+  int32_t width = mDecoder->GetFrameWidth();
+  int32_t height = mDecoder->GetFrameHeight();
+
+  // The V and U planes are stored 16-row-aligned, so we need to add padding
+  // to the row heights to ensure the Y'CbCr planes are referenced properly.
+  // YV12, planar format: [YYYY....][VVVV....][UUUU....]
+  // i.e., Y, then V, then U.
+  uint32_t padding = 0;
+  if (height % 16 != 0) {
+    padding = 16 - (height % 16);
+  }
+  int32_t y_size = stride * (height + padding);
+  int32_t v_size = stride * (height + padding) / 4;
+  int32_t halfStride = (stride + 1) / 2;
+  int32_t halfHeight = (height + 1) / 2;
+  int32_t halfWidth = (width + 1) / 2;
+  int32_t totalSize = y_size + 2 * v_size;
+
+  GMPSyncRunOnMainThread(WrapTask(aVideoFrame,
+                                  &GMPVideoi420Frame::CreateEmptyFrame,
+                                  stride, height, stride, halfStride, halfStride));
+
+  auto err = aVideoFrame->SetWidth(width);
+  ENSURE(GMP_SUCCEEDED(err), E_FAIL);
+  err = aVideoFrame->SetHeight(height);
+  ENSURE(GMP_SUCCEEDED(err), E_FAIL);
+
+  uint8_t* outBuffer = aVideoFrame->Buffer(kGMPYPlane);
+  ENSURE(outBuffer != nullptr, E_FAIL);
+  assert(aVideoFrame->AllocatedSize(kGMPYPlane) >= stride*height);
+  memcpy(outBuffer, data, stride*height);
+
+  outBuffer = aVideoFrame->Buffer(kGMPUPlane);
+  ENSURE(outBuffer != nullptr, E_FAIL);
+  assert(aVideoFrame->AllocatedSize(kGMPUPlane) >= halfStride*halfHeight);
+  memcpy(outBuffer, data+y_size, halfStride*halfHeight);
+
+  outBuffer = aVideoFrame->Buffer(kGMPVPlane);
+  ENSURE(outBuffer != nullptr, E_FAIL);
+  assert(aVideoFrame->AllocatedSize(kGMPVPlane) >= halfStride*halfHeight);
+  memcpy(outBuffer, data + y_size + v_size, halfStride*halfHeight);
+
+  if (twoDBuffer) {
+    twoDBuffer->Unlock2D();
+  } else {
+    mediaBuffer->Unlock();
+  }
+
+  LONGLONG hns = 0;
+  hr = aSample->GetSampleTime(&hns);
+  ENSURE(SUCCEEDED(hr), hr);
+  aVideoFrame->SetTimestamp(HNsToUsecs(hns));
+
+  hr = aSample->GetSampleDuration(&hns);
+  ENSURE(SUCCEEDED(hr), hr);
+  aVideoFrame->SetDuration(HNsToUsecs(hns));
+
+  return S_OK;
+}
+
+void
+VideoDecoder::Reset()
+{
+  mDecoder->Reset();
+  mCallback->ResetComplete();
+}
+
+void
+VideoDecoder::DrainTask()
+{
+  if (FAILED(mDecoder->Drain())) {
+    GMPSyncRunOnMainThread(WrapTask(mCallback, &GMPVideoDecoderCallback::DrainComplete));
+  }
+
+  // Return any pending output.
+  HRESULT hr = S_OK;
+  while (hr == S_OK) {
+    CComPtr<IMFSample> output;
+    hr = mDecoder->Output(&output);
+    SAMPLE_LOG(L"VideoDecoder::DrainTask() output ret=0x%x\n", hr);
+    if (hr == S_OK) {
+      ReturnOutput(output);
+    }
+  }
+  GMPSyncRunOnMainThread(WrapTask(mCallback, &GMPVideoDecoderCallback::DrainComplete));
+}
+
+void
+VideoDecoder::Drain()
+{
+  mWorkerThread->Post(WrapTask(this,
+                               &VideoDecoder::DrainTask));
+}
+
+void
+VideoDecoder::DecodingComplete()
+{
+  if (mWorkerThread) {
+    mWorkerThread->Join();
+  }
+  delete this;
+}
+
+#endif
new file mode 100644
--- /dev/null
+++ b/media/gmp-clearkey/0.1/VideoDecoder.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2013, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "stdafx.h"
+
+#ifdef TEST_DECODING
+
+class VideoDecoder : public GMPVideoDecoder
+{
+public:
+  VideoDecoder(GMPVideoHost *aHostAPI);
+
+  virtual ~VideoDecoder();
+
+  virtual void InitDecode(const GMPVideoCodec& aCodecSettings,
+                          const uint8_t* aCodecSpecific,
+                          uint32_t aCodecSpecificLength,
+                          GMPVideoDecoderCallback* aCallback,
+                          int32_t aCoreCount) override;
+
+  virtual void Decode(GMPVideoEncodedFrame* aInputFrame,
+                      bool aMissingFrames,
+                      const uint8_t* aCodecSpecific,
+                      uint32_t aCodecSpecificLength,
+                      int64_t aRenderTimeMs = -1);
+
+  virtual void Reset() override;
+
+  virtual void Drain() override;
+
+  virtual void DecodingComplete() override;
+
+private:
+
+  void DrainTask();
+
+  void DecodeTask(GMPVideoEncodedFrame* aInputFrame);
+
+  void ReturnOutput(IMFSample* aSample);
+
+  HRESULT SampleToVideoFrame(IMFSample* aSample,
+                             GMPVideoi420Frame* aVideoFrame);
+
+  GMPVideoHost *mHostAPI; // host-owned, invalid at DecodingComplete
+  GMPVideoDecoderCallback* mCallback; // host-owned, invalid at DecodingComplete
+  GMPThread* mWorkerThread;
+  GMPMutex* mMutex;
+  AutoPtr<WMFH264Decoder> mDecoder;
+
+  std::vector<uint8_t> mExtraData;
+  AVCDecoderConfigurationRecord mAVCC;
+  std::vector<uint8_t> mAnnexB;
+
+  int32_t mNumInputTasks;
+  bool mSentExtraData;
+};
+
+#endif
new file mode 100644
--- /dev/null
+++ b/media/gmp-clearkey/0.1/WMFAACDecoder.cpp
@@ -0,0 +1,371 @@
+/*
+ * Copyright 2013, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "stdafx.h"
+
+#ifdef TEST_DECODING
+
+using std::vector;
+using std::unique_ptr;
+
+WMFAACDecoder::WMFAACDecoder()
+  : mDecoder(nullptr)
+  , mChannels(0)
+  , mRate(0)
+{
+  memset(&mInputStreamInfo, 0, sizeof(MFT_INPUT_STREAM_INFO));
+  memset(&mOutputStreamInfo, 0, sizeof(MFT_OUTPUT_STREAM_INFO));
+}
+
+WMFAACDecoder::~WMFAACDecoder()
+{
+  Reset();
+}
+
+HRESULT
+AACAudioSpecificConfigToUserData(BYTE* aAudioSpecConfig, UINT32 aConfigLength,
+                                 BYTE** aOutUserData, UINT32* aOutUserDataLength)
+{
+  // For MFAudioFormat_AAC, MF_MT_USER_DATA
+  // Contains the portion of the HEAACWAVEINFO structure that appears
+  // after the WAVEFORMATEX structure (that is, after the wfx member).
+  // This is followed by the AudioSpecificConfig() data, as defined
+  // by ISO/IEC 14496-3.
+  // ...
+  // The length of the AudioSpecificConfig() data is 2 bytes for AAC-LC
+  // or HE-AAC with implicit signaling of SBR/PS. It is more than 2 bytes
+  // for HE-AAC with explicit signaling of SBR/PS.
+  //
+  // The value of audioObjectType as defined in AudioSpecificConfig()
+  // must be 2, indicating AAC-LC. The value of extensionAudioObjectType
+  // must be 5 for SBR or 29 for PS.
+  //
+  // See:
+  // http://msdn.microsoft.com/en-us/library/windows/desktop/dd742784%28v=vs.85%29.aspx
+  //
+  // HEAACWAVEINFO structure:
+  //    typedef struct heaacwaveinfo_tag {
+  //      WAVEFORMATEX wfx;
+  //      WORD         wPayloadType;
+  //      WORD         wAudioProfileLevelIndication;
+  //      WORD         wStructType;
+  //      WORD         wReserved1;
+  //      DWORD        dwReserved2;
+  //    }
+  const UINT32 heeInfoLen = 4 * sizeof(WORD) + sizeof(DWORD);
+  BYTE heeInfo[heeInfoLen] = {0};
+  WORD* w = (WORD*)heeInfo;
+  w[0] = 0x0; // Payload type raw AAC
+  w[1] = 0; // Profile level unspecified
+
+  const UINT32 len =  heeInfoLen + aConfigLength;
+  BYTE* data = new BYTE[len];
+  memcpy(data, heeInfo, heeInfoLen);
+  memcpy(data+heeInfoLen, aAudioSpecConfig, aConfigLength);
+  *aOutUserData = data;
+  *aOutUserDataLength = len;
+  return S_OK;
+}
+
+HRESULT
+WMFAACDecoder::Init(int32_t aChannelCount,
+                    int32_t aSampleRate,
+                    BYTE* aAACAudioSpecificConfig,
+                    UINT32 aAudioConfigLength)
+{
+  HRESULT hr;
+
+  // AAC decoder is in msauddecmft on Win8, and msmpeg2adec in earlier versions.
+  hr = CreateMFT(CLSID_CMSAACDecMFT,
+                 L"msauddecmft.dll",
+                 mDecoder);
+  if (FAILED(hr)) {
+    hr = CreateMFT(CLSID_CMSAACDecMFT,
+                   L"msmpeg2adec.dll",
+                   mDecoder);
+    if (FAILED(hr)) {
+      LOG(L"Failed to create AAC decoder\n");
+      return E_FAIL;
+    }
+  }
+
+  BYTE* userData = nullptr;
+  UINT32 userDataLength;
+  hr = AACAudioSpecificConfigToUserData(aAACAudioSpecificConfig,
+                                        aAudioConfigLength,
+                                        &userData,
+                                        &userDataLength);
+  ENSURE(SUCCEEDED(hr), hr);
+  hr = SetDecoderInputType(aChannelCount, aSampleRate, userData, userDataLength);
+  delete userData;
+  ENSURE(SUCCEEDED(hr), hr);
+
+  hr = SetDecoderOutputType();
+  ENSURE(SUCCEEDED(hr), hr);
+
+  hr = SendMFTMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  hr = SendMFTMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  hr = mDecoder->GetInputStreamInfo(0, &mInputStreamInfo);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  hr = mDecoder->GetOutputStreamInfo(0, &mOutputStreamInfo);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  return S_OK;
+}
+
+HRESULT
+WMFAACDecoder::SetDecoderInputType(int32_t aChannelCount,
+                                   int32_t aSampleRate,
+                                   BYTE* aUserData,
+                                   UINT32 aUserDataLength)
+{
+  HRESULT hr;
+
+  CComPtr<IMFMediaType> type;
+  hr = MFCreateMediaType(&type);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  hr = type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  hr = type->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_AAC);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  mRate = aSampleRate;
+  hr = type->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, mRate);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  mChannels = aChannelCount;
+  hr = type->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, mChannels);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  hr = type->SetUINT32(MF_MT_AAC_PAYLOAD_TYPE, 0x0); // Raw AAC
+  ENSURE(SUCCEEDED(hr), hr);
+
+  hr = type->SetBlob(MF_MT_USER_DATA, aUserData, aUserDataLength);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  hr = mDecoder->SetInputType(0, type, 0);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  return S_OK;
+}
+
+HRESULT
+WMFAACDecoder::SetDecoderOutputType()
+{
+  HRESULT hr;
+
+  CComPtr<IMFMediaType> type;
+
+  UINT32 typeIndex = 0;
+  while (type = nullptr, SUCCEEDED(mDecoder->GetOutputAvailableType(0, typeIndex++, &type))) {
+    GUID subtype;
+    hr = type->GetGUID(MF_MT_SUBTYPE, &subtype);
+    if (FAILED(hr)) {
+      continue;
+    }
+    if (subtype == MFAudioFormat_PCM) {
+      hr = mDecoder->SetOutputType(0, type, 0);
+      ENSURE(SUCCEEDED(hr), hr);
+
+      hr = type->GetUINT32(MF_MT_AUDIO_NUM_CHANNELS, &mChannels);
+      ENSURE(SUCCEEDED(hr), hr);
+
+      hr = type->GetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, &mRate);
+      ENSURE(SUCCEEDED(hr), hr);
+
+      return S_OK;
+    }
+  }
+
+  return E_FAIL;
+}
+
+HRESULT
+WMFAACDecoder::SendMFTMessage(MFT_MESSAGE_TYPE aMsg, UINT32 aData)
+{
+  ENSURE(mDecoder != nullptr, E_POINTER);
+  HRESULT hr = mDecoder->ProcessMessage(aMsg, aData);
+  ENSURE(SUCCEEDED(hr), hr);
+  return S_OK;
+}
+
+HRESULT
+WMFAACDecoder::CreateInputSample(const uint8_t* aData,
+                                 uint32_t aDataSize,
+                                 Microseconds aTimestamp,
+                                 IMFSample** aOutSample)
+{
+  HRESULT hr;
+  CComPtr<IMFSample> sample = nullptr;
+  hr = MFCreateSample(&sample);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  CComPtr<IMFMediaBuffer> buffer = nullptr;
+  int32_t bufferSize = std::max<uint32_t>(uint32_t(mInputStreamInfo.cbSize), aDataSize);
+  UINT32 alignment = (mInputStreamInfo.cbAlignment > 1) ? mInputStreamInfo.cbAlignment - 1 : 0;
+  hr = MFCreateAlignedMemoryBuffer(bufferSize, alignment, &buffer);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  DWORD maxLength = 0;
+  DWORD currentLength = 0;
+  BYTE* dst = nullptr;
+  hr = buffer->Lock(&dst, &maxLength, &currentLength);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  // Copy data into sample's buffer.
+  memcpy(dst, aData, aDataSize);
+
+  hr = buffer->Unlock();
+  ENSURE(SUCCEEDED(hr), hr);
+
+  hr = buffer->SetCurrentLength(aDataSize);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  hr = sample->AddBuffer(buffer);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  hr = sample->SetSampleTime(UsecsToHNs(aTimestamp));
+  ENSURE(SUCCEEDED(hr), hr);
+
+  *aOutSample = sample.Detach();
+
+  return S_OK;
+}
+
+HRESULT
+WMFAACDecoder::CreateOutputSample(IMFSample** aOutSample)
+{
+  HRESULT hr;
+  CComPtr<IMFSample> sample = nullptr;
+  hr = MFCreateSample(&sample);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  CComPtr<IMFMediaBuffer> buffer = nullptr;
+  int32_t bufferSize = mOutputStreamInfo.cbSize;
+  UINT32 alignment = (mOutputStreamInfo.cbAlignment > 1) ? mOutputStreamInfo.cbAlignment - 1 : 0;
+  hr = MFCreateAlignedMemoryBuffer(bufferSize, alignment, &buffer);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  DWORD maxLength = 0;
+  DWORD currentLength = 0;
+  BYTE* dst = nullptr;
+
+  hr = sample->AddBuffer(buffer);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  *aOutSample = sample.Detach();
+
+  return S_OK;
+}
+
+
+HRESULT
+WMFAACDecoder::GetOutputSample(IMFSample** aOutSample)
+{
+  HRESULT hr;
+  // We allocate samples for MFT output.
+  MFT_OUTPUT_DATA_BUFFER output = {0};
+
+  CComPtr<IMFSample> sample = nullptr;
+  hr = CreateOutputSample(&sample);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  output.pSample = sample;
+
+  DWORD status = 0;
+  hr = mDecoder->ProcessOutput(0, 1, &output, &status);
+  CComPtr<IMFCollection> events = output.pEvents; // Ensure this is released.
+
+  if (hr == MF_E_TRANSFORM_STREAM_CHANGE) {
+    // Type change. Probably geometric apperature change.
+    hr = SetDecoderOutputType();
+    ENSURE(SUCCEEDED(hr), hr);
+
+    return GetOutputSample(aOutSample);
+  } else if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT || !sample) {
+    return MF_E_TRANSFORM_NEED_MORE_INPUT;
+  }
+  // Treat other errors as fatal.
+  ENSURE(SUCCEEDED(hr), hr);
+
+  assert(sample);
+
+  *aOutSample = sample.Detach();
+  return S_OK;
+}
+
+HRESULT
+WMFAACDecoder::Input(const uint8_t* aData,
+                     uint32_t aDataSize,
+                     Microseconds aTimestamp)
+{
+  CComPtr<IMFSample> input = nullptr;
+  HRESULT hr = CreateInputSample(aData, aDataSize, aTimestamp, &input);
+  ENSURE(SUCCEEDED(hr) && input!=nullptr, hr);
+
+  hr = mDecoder->ProcessInput(0, input, 0);
+  if (hr == MF_E_NOTACCEPTING) {
+    // MFT *already* has enough data to produce a sample. Retrieve it.
+    LOG(L"ProcessInput returned MF_E_NOTACCEPTING\n");
+    return MF_E_NOTACCEPTING;
+  }
+  ENSURE(SUCCEEDED(hr), hr);
+
+  return S_OK;
+}
+
+HRESULT
+WMFAACDecoder::Output(IMFSample** aOutput)
+{
+  CComPtr<IMFSample> outputSample = nullptr;
+  HRESULT hr = GetOutputSample(&outputSample);
+  if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
+    return MF_E_TRANSFORM_NEED_MORE_INPUT;
+  }
+  // Treat other errors as fatal.
+  ENSURE(SUCCEEDED(hr) && outputSample, hr);
+
+  *aOutput = outputSample.Detach();
+
+  return S_OK;
+}
+
+HRESULT
+WMFAACDecoder::Reset()
+{
+  HRESULT hr = SendMFTMessage(MFT_MESSAGE_COMMAND_FLUSH, 0);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  return S_OK;
+}
+
+HRESULT
+WMFAACDecoder::Drain()
+{
+  HRESULT hr = SendMFTMessage(MFT_MESSAGE_COMMAND_DRAIN, 0);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  return S_OK;
+}
+
+#endif
new file mode 100644
--- /dev/null
+++ b/media/gmp-clearkey/0.1/WMFAACDecoder.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2013, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifdef TEST_DECODING
+
+#if !defined(WMFAACDecoder_h_)
+#define WMFAACDecoder_h_
+
+class WMFAACDecoder {
+public:
+  WMFAACDecoder();
+  ~WMFAACDecoder();
+
+  HRESULT Init(int32_t aChannelCount,
+               int32_t aSampleRate,
+               BYTE* aUserData,
+               UINT32 aUserDataLength);
+
+  HRESULT Input(const uint8_t* aData,
+                uint32_t aDataSize,
+                Microseconds aTimestamp);
+
+  HRESULT Output(IMFSample** aOutput);
+
+  HRESULT Reset();
+
+  HRESULT Drain();
+
+  UINT32 Channels() const { return mChannels; }
+  UINT32 Rate() const { return mRate; }
+
+private:
+
+  HRESULT GetOutputSample(IMFSample** aOutSample);
+  HRESULT CreateOutputSample(IMFSample** aOutSample);
+  HRESULT CreateInputSample(const uint8_t* aData,
+                            uint32_t aDataSize,
+                            Microseconds aTimestamp,
+                            IMFSample** aOutSample);
+
+  HRESULT SetDecoderInputType(int32_t aChannelCount,
+                              int32_t aSampleRate,
+                              BYTE* aUserData,
+                              UINT32 aUserDataLength);
+  HRESULT SetDecoderOutputType();
+  HRESULT SendMFTMessage(MFT_MESSAGE_TYPE aMsg, UINT32 aData);
+
+  MFT_INPUT_STREAM_INFO mInputStreamInfo;
+  MFT_OUTPUT_STREAM_INFO mOutputStreamInfo;
+
+  CComPtr<IMFTransform> mDecoder;
+
+  UINT32 mChannels;
+  UINT32 mRate;
+};
+
+#endif
+
+#endif
new file mode 100644
--- /dev/null
+++ b/media/gmp-clearkey/0.1/WMFH264Decoder.cpp
@@ -0,0 +1,343 @@
+/*
+ * Copyright 2013, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "stdafx.h"
+
+#ifdef TEST_DECODING
+
+WMFH264Decoder::WMFH264Decoder()
+  : mDecoder(nullptr)
+{
+  memset(&mInputStreamInfo, 0, sizeof(MFT_INPUT_STREAM_INFO));
+  memset(&mOutputStreamInfo, 0, sizeof(MFT_OUTPUT_STREAM_INFO));
+}
+
+WMFH264Decoder::~WMFH264Decoder()
+{
+}
+
+HRESULT
+WMFH264Decoder::Init()
+{
+  HRESULT hr;
+
+  hr = CreateMFT(__uuidof(CMSH264DecoderMFT),
+                 L"msmpeg2vdec.dll",
+                 mDecoder);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  hr = SetDecoderInputType();
+  ENSURE(SUCCEEDED(hr), hr);
+
+  hr = SetDecoderOutputType();
+  ENSURE(SUCCEEDED(hr), hr);
+
+  hr = SendMFTMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  hr = SendMFTMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  hr = mDecoder->GetInputStreamInfo(0, &mInputStreamInfo);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  hr = mDecoder->GetOutputStreamInfo(0, &mOutputStreamInfo);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  return S_OK;
+}
+
+HRESULT
+WMFH264Decoder::ConfigureVideoFrameGeometry(IMFMediaType* aMediaType)
+{
+  ENSURE(aMediaType != nullptr, E_POINTER);
+  HRESULT hr;
+
+  IntRect pictureRegion;
+  hr = ::GetPictureRegion(aMediaType, pictureRegion);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  UINT32 width = 0, height = 0;
+  hr = MFGetAttributeSize(aMediaType, MF_MT_FRAME_SIZE, &width, &height);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  // Success! Save state.
+  GetDefaultStride(aMediaType, (UINT32*)&mStride);
+  mVideoWidth = width;
+  mVideoHeight = height;
+  mPictureRegion = pictureRegion;
+
+  LOG(L"WMFH264Decoder frame geometry frame=(%u,%u) stride=%u picture=(%d, %d, %d, %d)\n",
+      width, height,
+      mStride,
+      mPictureRegion.x, mPictureRegion.y, mPictureRegion.width, mPictureRegion.height);
+
+  return S_OK;
+}
+
+int32_t
+WMFH264Decoder::GetFrameWidth() const
+{
+  return mVideoWidth;
+}
+
+int32_t
+WMFH264Decoder::GetFrameHeight() const
+{
+  return mVideoHeight;
+}
+
+const IntRect&
+WMFH264Decoder::GetPictureRegion() const
+{
+  return mPictureRegion;
+}
+
+int32_t
+WMFH264Decoder::GetStride() const
+{
+  return mStride;
+}
+
+HRESULT
+WMFH264Decoder::SetDecoderInputType()
+{
+  HRESULT hr;
+
+  CComPtr<IMFMediaType> type;
+  hr = ::MFCreateMediaType(&type);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  hr = type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  hr = type->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_H264);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  hr = type->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_MixedInterlaceOrProgressive);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  hr = mDecoder->SetInputType(0, type, 0);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  return S_OK;
+}
+
+HRESULT
+WMFH264Decoder::SetDecoderOutputType()
+{
+  HRESULT hr;
+
+  CComPtr<IMFMediaType> type;
+
+  UINT32 typeIndex = 0;
+  while (type = nullptr, SUCCEEDED(mDecoder->GetOutputAvailableType(0, typeIndex++, &type))) {
+    GUID subtype;
+    hr = type->GetGUID(MF_MT_SUBTYPE, &subtype);
+    if (FAILED(hr)) {
+      continue;
+    }
+    if (subtype == MFVideoFormat_I420) {
+      hr = mDecoder->SetOutputType(0, type, 0);
+      ENSURE(SUCCEEDED(hr), hr);
+
+      hr = ConfigureVideoFrameGeometry(type);
+      ENSURE(SUCCEEDED(hr), hr);
+
+      return S_OK;
+    }
+  }
+
+  return E_FAIL;
+}
+
+HRESULT
+WMFH264Decoder::SendMFTMessage(MFT_MESSAGE_TYPE aMsg, UINT32 aData)
+{
+  ENSURE(mDecoder != nullptr, E_POINTER);
+  HRESULT hr = mDecoder->ProcessMessage(aMsg, aData);
+  ENSURE(SUCCEEDED(hr), hr);
+  return S_OK;
+}
+
+HRESULT
+WMFH264Decoder::CreateInputSample(const uint8_t* aData,
+                                  uint32_t aDataSize,
+                                  Microseconds aTimestamp,
+                                  Microseconds aDuration,
+                                  IMFSample** aOutSample)
+{
+  HRESULT hr;
+  CComPtr<IMFSample> sample;
+  hr = MFCreateSample(&sample);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  CComPtr<IMFMediaBuffer> buffer;
+  int32_t bufferSize = std::max<uint32_t>(uint32_t(mInputStreamInfo.cbSize), aDataSize);
+  UINT32 alignment = (mInputStreamInfo.cbAlignment > 1) ? mInputStreamInfo.cbAlignment - 1 : 0;
+  hr = MFCreateAlignedMemoryBuffer(bufferSize, alignment, &buffer);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  DWORD maxLength = 0;
+  DWORD currentLength = 0;
+  BYTE* dst = nullptr;
+  hr = buffer->Lock(&dst, &maxLength, &currentLength);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  // Copy data into sample's buffer.
+  memcpy(dst, aData, aDataSize);
+
+  hr = buffer->Unlock();
+  ENSURE(SUCCEEDED(hr), hr);
+
+  hr = buffer->SetCurrentLength(aDataSize);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  hr = sample->AddBuffer(buffer);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  hr = sample->SetSampleTime(UsecsToHNs(aTimestamp));
+  ENSURE(SUCCEEDED(hr), hr);
+
+  sample->SetSampleDuration(UsecsToHNs(aDuration));
+
+  *aOutSample = sample.Detach();
+
+  return S_OK;
+}
+
+HRESULT
+WMFH264Decoder::CreateOutputSample(IMFSample** aOutSample)
+{
+  HRESULT hr;
+  CComPtr<IMFSample> sample;
+  hr = MFCreateSample(&sample);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  CComPtr<IMFMediaBuffer> buffer;
+  int32_t bufferSize = mOutputStreamInfo.cbSize;
+  UINT32 alignment = (mOutputStreamInfo.cbAlignment > 1) ? mOutputStreamInfo.cbAlignment - 1 : 0;
+  hr = MFCreateAlignedMemoryBuffer(bufferSize, alignment, &buffer);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  DWORD maxLength = 0;
+  DWORD currentLength = 0;
+  BYTE* dst = nullptr;
+
+  hr = sample->AddBuffer(buffer);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  *aOutSample = sample.Detach();
+
+  return S_OK;
+}
+
+
+HRESULT
+WMFH264Decoder::GetOutputSample(IMFSample** aOutSample)
+{
+  HRESULT hr;
+  // We allocate samples for MFT output.
+  MFT_OUTPUT_DATA_BUFFER output = {0};
+
+  CComPtr<IMFSample> sample;
+  hr = CreateOutputSample(&sample);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  output.pSample = sample;
+
+  DWORD status = 0;
+  hr = mDecoder->ProcessOutput(0, 1, &output, &status);
+  //LOG(L"WMFH264Decoder::GetOutputSample() ProcessOutput returned 0x%x\n", hr);
+  CComPtr<IMFCollection> events = output.pEvents; // Ensure this is released.
+
+  if (hr == MF_E_TRANSFORM_STREAM_CHANGE) {
+    // Type change. Probably geometric apperature change.
+    hr = SetDecoderOutputType();
+    ENSURE(SUCCEEDED(hr), hr);
+
+    return GetOutputSample(aOutSample);
+  } else if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
+    return MF_E_TRANSFORM_NEED_MORE_INPUT;
+  }
+  // Treat other errors as fatal.
+  ENSURE(SUCCEEDED(hr), hr);
+
+  assert(sample);
+
+  // output.pSample
+  *aOutSample = sample.Detach(); // AddRefs
+  return S_OK;
+}
+
+HRESULT
+WMFH264Decoder::Input(const uint8_t* aData,
+                      uint32_t aDataSize,
+                      Microseconds aTimestamp,
+                      Microseconds aDuration)
+{
+  HRESULT hr;
+  CComPtr<IMFSample> input = nullptr;
+  hr = CreateInputSample(aData, aDataSize, aTimestamp, aDuration, &input);
+  ENSURE(SUCCEEDED(hr) && input!=nullptr, hr);
+
+  hr = mDecoder->ProcessInput(0, input, 0);
+  if (hr == MF_E_NOTACCEPTING) {
+    // MFT *already* has enough data to produce a sample. Retrieve it.
+    LOG(L"ProcessInput returned MF_E_NOTACCEPTING\n");
+    return MF_E_NOTACCEPTING;
+  }
+  ENSURE(SUCCEEDED(hr), hr);
+
+  return S_OK;
+}
+
+HRESULT
+WMFH264Decoder::Output(IMFSample** aOutput)
+{
+  HRESULT hr;
+  CComPtr<IMFSample> outputSample;
+  hr = GetOutputSample(&outputSample);
+  if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
+    return MF_E_TRANSFORM_NEED_MORE_INPUT;
+  }
+  // Treat other errors as fatal.
+  ENSURE(SUCCEEDED(hr) && outputSample, hr);
+
+  *aOutput = outputSample.Detach();
+
+  return S_OK;
+}
+
+HRESULT
+WMFH264Decoder::Reset()
+{
+  HRESULT hr = SendMFTMessage(MFT_MESSAGE_COMMAND_FLUSH, 0);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  return S_OK;
+}
+
+HRESULT
+WMFH264Decoder::Drain()
+{
+  HRESULT hr = SendMFTMessage(MFT_MESSAGE_COMMAND_DRAIN, 0);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  return S_OK;
+}
+
+#endif
new file mode 100644
--- /dev/null
+++ b/media/gmp-clearkey/0.1/WMFH264Decoder.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2013, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifdef TEST_DECODING
+
+#if !defined(WMFH264Decoder_h_)
+#define WMFH264Decoder_h_
+
+
+class WMFH264Decoder {
+public:
+  WMFH264Decoder();
+  ~WMFH264Decoder();
+
+  HRESULT Init();
+
+  HRESULT Input(const uint8_t* aData,
+                uint32_t aDataSize,
+                Microseconds aTimestamp,
+                Microseconds aDuration);
+
+  HRESULT Output(IMFSample** aOutput);
+
+  HRESULT Reset();
+
+  int32_t GetFrameWidth() const;
+  int32_t GetFrameHeight() const;
+  const IntRect& GetPictureRegion() const;
+  int32_t GetStride() const;
+
+  HRESULT Drain();
+
+private:
+
+  HRESULT SetDecoderInputType();
+  HRESULT SetDecoderOutputType();
+  HRESULT SendMFTMessage(MFT_MESSAGE_TYPE aMsg, UINT32 aData);
+
+  HRESULT CreateInputSample(const uint8_t* aData,
+                            uint32_t aDataSize,
+                            Microseconds aTimestamp,
+                            Microseconds aDuration,
+                            IMFSample** aOutSample);
+
+  HRESULT CreateOutputSample(IMFSample** aOutSample);
+
+  HRESULT GetOutputSample(IMFSample** aOutSample);
+  HRESULT ConfigureVideoFrameGeometry(IMFMediaType* aMediaType);
+
+  MFT_INPUT_STREAM_INFO mInputStreamInfo;
+  MFT_OUTPUT_STREAM_INFO mOutputStreamInfo;
+
+  CComPtr<IMFTransform> mDecoder;
+
+  int32_t mVideoWidth;
+  int32_t mVideoHeight;
+  IntRect mPictureRegion;
+  int32_t mStride;
+
+};
+
+#endif
+
+#endif
new file mode 100644
--- /dev/null
+++ b/media/gmp-clearkey/0.1/WMFUtils.cpp
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2013, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "stdafx.h"
+
+void LOG(wchar_t* format, ...)
+{
+  va_list args;
+  va_start(args, format);
+
+  WCHAR msg[MAX_PATH];
+  if (FAILED(StringCbVPrintf(msg, sizeof(msg), format, args))) {
+    return;
+  }
+
+  OutputDebugString(msg);
+}
+
+
+#ifdef TEST_DECODING
+
+int32_t
+MFOffsetToInt32(const MFOffset& aOffset)
+{
+  return int32_t(aOffset.value + (aOffset.fract / 65536.0f));
+}
+
+// Gets the sub-region of the video frame that should be displayed.
+// See: http://msdn.microsoft.com/en-us/library/windows/desktop/bb530115(v=vs.85).aspx
+HRESULT
+GetPictureRegion(IMFMediaType* aMediaType, IntRect& aOutPictureRegion)
+{
+  // Determine if "pan and scan" is enabled for this media. If it is, we
+  // only display a region of the video frame, not the entire frame.
+  BOOL panScan = MFGetAttributeUINT32(aMediaType, MF_MT_PAN_SCAN_ENABLED, FALSE);
+
+  // If pan and scan mode is enabled. Try to get the display region.
+  HRESULT hr = E_FAIL;
+  MFVideoArea videoArea;
+  memset(&videoArea, 0, sizeof(MFVideoArea));
+  if (panScan) {
+    hr = aMediaType->GetBlob(MF_MT_PAN_SCAN_APERTURE,
+                             (UINT8*)&videoArea,
+                             sizeof(MFVideoArea),
+                             NULL);
+  }
+
+  // If we're not in pan-and-scan mode, or the pan-and-scan region is not set,
+  // check for a minimimum display aperture.
+  if (!panScan || hr == MF_E_ATTRIBUTENOTFOUND) {
+    hr = aMediaType->GetBlob(MF_MT_MINIMUM_DISPLAY_APERTURE,
+                             (UINT8*)&videoArea,
+                             sizeof(MFVideoArea),
+                             NULL);
+  }
+
+  if (hr == MF_E_ATTRIBUTENOTFOUND) {
+    // Minimum display aperture is not set, for "backward compatibility with
+    // some components", check for a geometric aperture.
+    hr = aMediaType->GetBlob(MF_MT_GEOMETRIC_APERTURE,
+                             (UINT8*)&videoArea,
+                             sizeof(MFVideoArea),
+                             NULL);
+  }
+
+  if (SUCCEEDED(hr)) {
+    // The media specified a picture region, return it.
+    aOutPictureRegion = IntRect(MFOffsetToInt32(videoArea.OffsetX),
+                                  MFOffsetToInt32(videoArea.OffsetY),
+                                  videoArea.Area.cx,
+                                  videoArea.Area.cy);
+    return S_OK;
+  }
+
+  // No picture region defined, fall back to using the entire video area.
+  UINT32 width = 0, height = 0;
+  hr = MFGetAttributeSize(aMediaType, MF_MT_FRAME_SIZE, &width, &height);
+  ENSURE(SUCCEEDED(hr), hr);
+  aOutPictureRegion = IntRect(0, 0, width, height);
+  return S_OK;
+}
+
+
+HRESULT
+GetDefaultStride(IMFMediaType *aType, uint32_t* aOutStride)
+{
+  // Try to get the default stride from the media type.
+  HRESULT hr = aType->GetUINT32(MF_MT_DEFAULT_STRIDE, aOutStride);
+  if (SUCCEEDED(hr)) {
+    return S_OK;
+  }
+
+  // Stride attribute not set, calculate it.
+  GUID subtype = GUID_NULL;
+  uint32_t width = 0;
+  uint32_t height = 0;
+
+  hr = aType->GetGUID(MF_MT_SUBTYPE, &subtype);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  hr = MFGetAttributeSize(aType, MF_MT_FRAME_SIZE, &width, &height);
+  ENSURE(SUCCEEDED(hr), hr);
+
+  hr = MFGetStrideForBitmapInfoHeader(subtype.Data1, width, (LONG*)(aOutStride));
+  ENSURE(SUCCEEDED(hr), hr);
+
+  return hr;
+}
+
+#endif
+
+void dump(const uint8_t* data, uint32_t len, const char* filename)
+{
+  FILE* f = 0;
+  fopen_s(&f, filename, "wb");
+  fwrite(data, len, 1, f);
+  fclose(f);
+}
+
+HRESULT
+CreateMFT(const CLSID& clsid,
+          const wchar_t* aDllName,
+          CComPtr<IMFTransform>& aOutMFT)
+{
+  HMODULE module = ::GetModuleHandle(aDllName);
+  if (!module) {
+    LOG(L"Failed to get %S\n", aDllName);
+    return E_FAIL;
+  }
+
+  typedef HRESULT (WINAPI* DllGetClassObjectFnPtr)(const CLSID& clsid,
+                                                   const IID& iid,
+                                                   void** object);
+
+  DllGetClassObjectFnPtr GetClassObjPtr =
+    reinterpret_cast<DllGetClassObjectFnPtr>(GetProcAddress(module, "DllGetClassObject"));
+  if (!GetClassObjPtr) {
+    LOG(L"Failed to get DllGetClassObject\n");
+    return E_FAIL;
+  }
+
+  CComPtr<IClassFactory> classFactory;
+  HRESULT hr = GetClassObjPtr(clsid,
+                              __uuidof(IClassFactory),
+                              reinterpret_cast<void**>(static_cast<IClassFactory**>(&classFactory)));
+  if (FAILED(hr)) {
+    LOG(L"Failed to get H264 IClassFactory\n");
+    return E_FAIL;
+  }
+
+  hr = classFactory->CreateInstance(NULL,
+                                    __uuidof(IMFTransform),
+                                    reinterpret_cast<void**>(static_cast<IMFTransform**>(&aOutMFT)));
+  if (FAILED(hr)) {
+    LOG(L"Failed to get create MFT\n");
+    return E_FAIL;
+  }
+
+  return S_OK;
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/media/gmp-clearkey/0.1/WMFUtils.h
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2013, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+void LOG(wchar_t* format, ...);
+
+#ifdef LOG_SAMPLE_DECODE
+#define SAMPLE_LOG LOG
+#else
+#define SAMPLE_LOG(...)
+#endif
+
+class IntRect {
+public:
+  IntRect(int32_t _x, int32_t _y, int32_t _w, int32_t _h)
+    : x(_x), y(_y), width(_w), height(_h) {}
+  IntRect()
+    : x(0), y(0), width(0), height(0) {}
+  int32_t x;
+  int32_t y;
+  int32_t width;
+  int32_t height;
+};
+
+typedef int64_t Microseconds;
+
+#define ENSURE(condition, ret) \
+{ if (!(condition)) { LOG(L"##condition## FAILED %S:%d\n", __FILE__, __LINE__); return ret; } }
+
+#define GMP_SUCCEEDED(x) ((x) == GMPNoErr)
+#define GMP_FAILED(x) ((x) != GMPNoErr)
+
+HRESULT
+GetPictureRegion(IMFMediaType* aMediaType, IntRect& aOutPictureRegion);
+
+HRESULT
+GetDefaultStride(IMFMediaType *aType, uint32_t* aOutStride);
+
+// Converts from microseconds to hundreds of nanoseconds.
+// We use microseconds for our timestamps, whereas WMF uses
+// hundreds of nanoseconds.
+inline int64_t
+UsecsToHNs(int64_t aUsecs) {
+  return aUsecs * 10;
+}
+
+// Converts from hundreds of nanoseconds to microseconds.
+// We use microseconds for our timestamps, whereas WMF uses
+// hundreds of nanoseconds.
+inline int64_t
+HNsToUsecs(int64_t hNanoSecs) {
+  return hNanoSecs / 10;
+}
+
+inline std::string narrow(std::wstring &wide) {
+  std::string ns(wide.begin(), wide.end());
+  return ns;
+}
+
+inline std::wstring widen(std::string &narrow) {
+  std::wstring ws(narrow.begin(), narrow.end());
+  return ws;
+}
+
+#define ARRAY_LENGTH(array_) \
+  (sizeof(array_)/sizeof(array_[0]))
+
+template<class Type>
+class AutoPtr {
+public:
+  AutoPtr()
+    : mPtr(nullptr)
+  {
+  }
+
+  AutoPtr(AutoPtr<Type>& aPtr)
+    : mPtr(aPtr.Forget())
+  {
+  }
+
+  AutoPtr(Type* aPtr)
+    : mPtr(aPtr)
+  {
+  }
+
+  ~AutoPtr() {
+    if (mPtr) {
+      delete mPtr;
+    }
+  }
+
+  Type* Forget() {
+    Type* rv = mPtr;
+    mPtr = nullptr;
+    return rv;
+  }
+
+  AutoPtr<Type>& operator=(Type* aOther) {
+    Assign(aOther);
+    return *this;
+  }
+
+  AutoPtr<Type>& operator=(AutoPtr<Type>& aOther) {
+    Assign(aOther.Forget());
+    return *this;
+  }
+
+  Type* Get() const {
+    return mPtr;
+  }
+
+  Type* operator->() const {
+    assert(mPtr);
+    return Get();
+  }
+
+  operator Type*() const {
+    return Get();
+  }
+
+  Type** Receive() {
+    return &mPtr;
+  }
+private:
+
+  void Assign(Type* aPtr) {
+    if (mPtr) {
+      delete mPtr;
+    }
+    mPtr = aPtr;
+  }
+
+  Type* mPtr;
+};
+
+// Video frame microseconds are (currently) in 90kHz units, as used by RTP.
+// Use this to convert to microseconds...
+inline Microseconds RTPTimeToMicroseconds(int64_t rtptime) {
+  return (rtptime * 1000000) / 90000;
+}
+
+inline uint32_t MicrosecondsToRTPTime(Microseconds us) {
+  return uint32_t(0xffffffff & (us * 90000) / 1000000);
+}
+
+class AutoLock {
+public:
+  AutoLock(GMPMutex* aMutex)
+    : mMutex(aMutex)
+  {
+    assert(aMutex);
+    mMutex->Acquire();
+  }
+  ~AutoLock() {
+    mMutex->Release();
+  }
+private:
+  GMPMutex* mMutex;
+};
+
+void dump(const uint8_t* data, uint32_t len, const char* filename);
+
+HRESULT
+CreateMFT(const CLSID& clsid,
+          const wchar_t* aDllName,
+          CComPtr<IMFTransform>& aOutMFT);
--- a/media/gmp-clearkey/0.1/moz.build
+++ b/media/gmp-clearkey/0.1/moz.build
@@ -4,24 +4,29 @@
 # 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/.
 
 SharedLibrary('clearkey')
 
 FINAL_TARGET = 'dist/bin/gmp-clearkey/0.1'
 
 UNIFIED_SOURCES += [
+    'AudioDecoder.cpp',
     'ClearKeyDecryptionManager.cpp',
     'ClearKeyPersistence.cpp',
     'ClearKeySession.cpp',
     'ClearKeySessionManager.cpp',
     'ClearKeyStorage.cpp',
     'ClearKeyUtils.cpp',
     'gmp-clearkey.cpp',
     'openaes/oaes_lib.c',
+    'VideoDecoder.cpp',
+    'WMFAACDecoder.cpp',
+    'WMFH264Decoder.cpp',
+    'WMFUtils.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '/dom/media/gmp',
 ]
 
 USE_STATIC_LIBS = True