/* * 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 "WMFH264Decoder.h" #include #include namespace wmf { 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(int32_t aCoreCount) { HRESULT hr; hr = CreateMFT(__uuidof(CMSH264DecoderMFT), WMFDecoderDllName(), mDecoder); if (FAILED(hr)) { // Windows 7 Enterprise Server N (which is what Mozilla's mochitests run // on) need a different CLSID to instantiate the H.264 decoder. hr = CreateMFT(CLSID_CMSH264DecMFT, WMFDecoderDllName(), mDecoder); } ENSURE(SUCCEEDED(hr), hr); CComPtr attr; hr = mDecoder->GetAttributes(&attr); ENSURE(SUCCEEDED(hr), hr); hr = attr->SetUINT32(CODECAPI_AVDecNumWorkerThreads, GetNumThreads(aCoreCount)); 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 = wmf::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); ENSURE(width <= mozilla::MAX_VIDEO_WIDTH, E_FAIL); ENSURE(height <= mozilla::MAX_VIDEO_HEIGHT, E_FAIL); UINT32 stride = 0; hr = GetDefaultStride(aMediaType, &stride); ENSURE(SUCCEEDED(hr), hr); ENSURE(stride <= mozilla::MAX_VIDEO_WIDTH, E_FAIL); // Success! Save state. mStride = stride; mVideoWidth = width; mVideoHeight = height; mPictureRegion = pictureRegion; LOG("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::GetFrameHeight() const { return mVideoHeight; } const IntRect& WMFH264Decoder::GetPictureRegion() const { return mPictureRegion; } int32_t WMFH264Decoder::GetStride() const { return mStride; } HRESULT WMFH264Decoder::SetDecoderInputType() { HRESULT hr; CComPtr 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 type; UINT32 typeIndex = 0; while (static_cast(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 || subtype == MFVideoFormat_IYUV) { // On Windows 7 Enterprise N the MFT reports it reports IYUV instead // of I420. Other Windows' report I420. The formats are the same, so // support both. 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, IMFSample** aOutSample) { HRESULT hr; CComPtr sample; hr = MFCreateSample(&sample); ENSURE(SUCCEEDED(hr), hr); CComPtr buffer; int32_t bufferSize = std::max(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, ¤tLength); 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 WMFH264Decoder::CreateOutputSample(IMFSample** aOutSample) { HRESULT hr; CComPtr sample; hr = MFCreateSample(&sample); ENSURE(SUCCEEDED(hr), hr); CComPtr buffer; int32_t bufferSize = mOutputStreamInfo.cbSize; UINT32 alignment = (mOutputStreamInfo.cbAlignment > 1) ? mOutputStreamInfo.cbAlignment - 1 : 0; hr = MFCreateAlignedMemoryBuffer(bufferSize, alignment, &buffer); ENSURE(SUCCEEDED(hr), hr); 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 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 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) { HRESULT hr; CComPtr input = nullptr; 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("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 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; } } // namespace wmf