| // Copyright (c) 2014 The WebM project authors. All Rights Reserved. |
| // |
| // Use of this source code is governed by a BSD-style license |
| // that can be found in the LICENSE file in the root of the source |
| // tree. An additional intellectual property rights grant can be found |
| // in the file PATENTS. All contributing project authors may |
| // be found in the AUTHORS file in the root of the source tree. |
| |
| #pragma warning(disable : 4505) // unreferenced local function removed |
| |
| #include "webmmfvp8dec.h" |
| |
| #include <comdef.h> |
| |
| #include <cassert> |
| #include <new> |
| |
| #include "libyuv_util.h" |
| |
| #ifdef _DEBUG |
| #include "odbgstream.h" |
| #include "iidstr.h" |
| using std::endl; |
| using std::boolalpha; |
| #endif |
| |
| _COM_SMARTPTR_TYPEDEF(IMFMediaBuffer, __uuidof(IMFMediaBuffer)); |
| _COM_SMARTPTR_TYPEDEF(IMF2DBuffer, __uuidof(IMF2DBuffer)); |
| |
| namespace WebmMfVp8DecLib { |
| |
| const int kNumInputMediaSubtypes = 2; |
| const GUID kInputMediaSubtypes[kNumInputMediaSubtypes] = { |
| WebmTypes::MEDIASUBTYPE_VP80, |
| WebmTypes::MEDIASUBTYPE_VP90, |
| }; |
| |
| HRESULT CreateDecoder(IClassFactory* pClassFactory, IUnknown* pOuter, |
| const IID& iid, void** ppv) { |
| if (ppv == 0) |
| return E_POINTER; |
| |
| *ppv = 0; |
| |
| if (pOuter) |
| return CLASS_E_NOAGGREGATION; |
| |
| WebmMfVp8Dec* const p = new (std::nothrow) WebmMfVp8Dec(pClassFactory); |
| |
| if (p == 0) |
| return E_OUTOFMEMORY; |
| |
| IMFTransform* const pUnk = p; |
| |
| const HRESULT hr = pUnk->QueryInterface(iid, ppv); |
| |
| const ULONG cRef = pUnk->Release(); |
| cRef; |
| |
| return hr; |
| } |
| |
| WebmMfVp8Dec::WebmMfVp8Dec(IClassFactory* pClassFactory) |
| : m_pClassFactory(pClassFactory), |
| m_cRef(1), |
| m_pInputMediaType(0), |
| m_pOutputMediaType(0), |
| m_scaled_image(0), |
| m_rate(1), |
| m_bThin(FALSE), |
| m_drop_mode(MF_DROP_MODE_NONE), |
| m_drop_budget(0), |
| m_lag_sum(0), |
| m_lag_count(0), |
| m_lag_frames(0), |
| m_frame_rate(-1) { |
| HRESULT hr = m_pClassFactory->LockServer(TRUE); |
| assert(SUCCEEDED(hr)); |
| |
| hr = CLockable::Init(); |
| assert(SUCCEEDED(hr)); |
| |
| // m_frame_rate.Init(); |
| } |
| |
| WebmMfVp8Dec::~WebmMfVp8Dec() { |
| if (m_pInputMediaType) { |
| const ULONG n = m_pInputMediaType->Release(); |
| n; |
| assert(n == 0); |
| |
| m_pInputMediaType = 0; |
| |
| const vpx_codec_err_t e = vpx_codec_destroy(&m_ctx); |
| e; |
| assert(e == VPX_CODEC_OK); |
| } |
| |
| vpx_img_free(m_scaled_image); |
| m_scaled_image = NULL; |
| |
| if (m_pOutputMediaType) { |
| const ULONG n = m_pOutputMediaType->Release(); |
| n; |
| assert(n == 0); |
| |
| m_pOutputMediaType = 0; |
| } |
| |
| Flush(); |
| |
| HRESULT hr = m_pClassFactory->LockServer(FALSE); |
| assert(SUCCEEDED(hr)); |
| } |
| |
| HRESULT WebmMfVp8Dec::QueryInterface(const IID& iid, void** ppv) { |
| if (ppv == 0) |
| return E_POINTER; |
| |
| IUnknown*& pUnk = reinterpret_cast<IUnknown*&>(*ppv); |
| |
| if (iid == __uuidof(IUnknown)) { |
| pUnk = static_cast<IMFTransform*>(this); // must be nondelegating |
| } else if (iid == __uuidof(IMFTransform)) { |
| pUnk = static_cast<IMFTransform*>(this); |
| } else if (iid == __uuidof(IMFQualityAdvise2)) { |
| pUnk = static_cast<IMFQualityAdvise2*>(this); |
| } else if (iid == __uuidof(IMFQualityAdvise)) { |
| pUnk = static_cast<IMFQualityAdvise*>(this); |
| } else if (iid == __uuidof(IMFRateControl)) { |
| pUnk = static_cast<IMFRateControl*>(this); |
| } else if (iid == __uuidof(IMFRateSupport)) { |
| pUnk = static_cast<IMFRateSupport*>(this); |
| } else if (iid == __uuidof(IMFGetService)) { |
| pUnk = static_cast<IMFGetService*>(this); |
| } else { |
| //#ifdef _DEBUG |
| // wodbgstream os; |
| // os << "WebmMfVp8Dec::QI: iid=" << IIDStr(iid) << std::endl; |
| //#endif |
| |
| pUnk = 0; |
| return E_NOINTERFACE; |
| } |
| |
| pUnk->AddRef(); |
| return S_OK; |
| } |
| |
| ULONG WebmMfVp8Dec::AddRef() { return InterlockedIncrement(&m_cRef); } |
| |
| ULONG WebmMfVp8Dec::Release() { |
| if (LONG n = InterlockedDecrement(&m_cRef)) |
| return n; |
| |
| delete this; |
| return 0; |
| } |
| |
| HRESULT WebmMfVp8Dec::GetStreamLimits(DWORD* pdwInputMin, DWORD* pdwInputMax, |
| DWORD* pdwOutputMin, |
| DWORD* pdwOutputMax) { |
| if (pdwInputMin == 0) |
| return E_POINTER; |
| |
| if (pdwInputMax == 0) |
| return E_POINTER; |
| |
| if (pdwOutputMin == 0) |
| return E_POINTER; |
| |
| if (pdwOutputMax == 0) |
| return E_POINTER; |
| |
| DWORD& dwInputMin = *pdwInputMin; |
| dwInputMin = 1; |
| |
| DWORD& dwInputMax = *pdwInputMax; |
| dwInputMax = 1; |
| |
| DWORD& dwOutputMin = *pdwOutputMin; |
| dwOutputMin = 1; |
| |
| DWORD& dwOutputMax = *pdwOutputMax; |
| dwOutputMax = 1; |
| |
| return S_OK; |
| } |
| |
| HRESULT WebmMfVp8Dec::GetStreamCount(DWORD* pcInputStreams, |
| DWORD* pcOutputStreams) { |
| if (pcInputStreams == 0) |
| return E_POINTER; |
| |
| if (pcOutputStreams == 0) |
| return E_POINTER; |
| |
| DWORD& cInputStreams = *pcInputStreams; |
| cInputStreams = 1; |
| |
| DWORD& cOutputStreams = *pcOutputStreams; |
| cOutputStreams = 1; |
| |
| return S_OK; |
| } |
| |
| HRESULT WebmMfVp8Dec::GetStreamIDs(DWORD, DWORD* pdwInputIDs, DWORD, |
| DWORD* pdwOutputIDs) { |
| if (pdwInputIDs) |
| *pdwInputIDs = 0; |
| |
| if (pdwOutputIDs) |
| *pdwOutputIDs = 0; |
| |
| return E_NOTIMPL; |
| } |
| |
| HRESULT WebmMfVp8Dec::GetInputStreamInfo(DWORD dwInputStreamID, |
| MFT_INPUT_STREAM_INFO* pStreamInfo) { |
| if (pStreamInfo == 0) |
| return E_POINTER; |
| |
| if (dwInputStreamID != 0) |
| return MF_E_INVALIDSTREAMNUMBER; |
| |
| MFT_INPUT_STREAM_INFO& info = *pStreamInfo; |
| |
| // LONGLONG hnsMaxLatency; |
| // DWORD dwFlags; |
| // DWORD cbSize; |
| // DWORD cbMaxLookahead; |
| // DWORD cbAlignment; |
| |
| info.cbMaxLookahead = 0; |
| info.hnsMaxLatency = 0; // TODO: Is 0 correct? |
| // TODO: does lag-in-frames matter here? |
| // See "_MFT_INPUT_STREAM_INFO_FLAGS Enumeration" for more info: |
| // http://msdn.microsoft.com/en-us/library/ms703975%28v=VS.85%29.aspx |
| |
| // enum _MFT_INPUT_STREAM_INFO_FLAGS |
| // { MFT_INPUT_STREAM_WHOLE_SAMPLES = 0x1, |
| // MFT_INPUT_STREAM_SINGLE_SAMPLE_PER_BUFFER = 0x2, |
| // MFT_INPUT_STREAM_FIXED_SAMPLE_SIZE = 0x4, |
| // MFT_INPUT_STREAM_HOLDS_BUFFERS = 0x8, |
| // MFT_INPUT_STREAM_DOES_NOT_ADDREF = 0x100, |
| // MFT_INPUT_STREAM_REMOVABLE= 0x200, |
| // MFT_INPUT_STREAM_OPTIONAL = 0x400, |
| // MFT_INPUT_STREAM_PROCESSES_IN_PLACE = 0x800 |
| // }; |
| |
| info.dwFlags = MFT_INPUT_STREAM_WHOLE_SAMPLES; |
| // MFT_INPUT_STREAM_SINGLE_SAMPLE_PER_BUFFER; |
| // MFT_INPUT_STREAM_DOES_NOT_ADDREF; |
| |
| info.cbSize = 0; // input size is variable |
| info.cbAlignment = 0; // no specific alignment requirements |
| |
| return S_OK; |
| } |
| |
| HRESULT WebmMfVp8Dec::GetOutputStreamInfo(DWORD dwOutputStreamID, |
| MFT_OUTPUT_STREAM_INFO* pStreamInfo) { |
| if (pStreamInfo == 0) |
| return E_POINTER; |
| |
| if (dwOutputStreamID != 0) |
| return MF_E_INVALIDSTREAMNUMBER; |
| |
| Lock lock; |
| |
| const HRESULT hr = lock.Seize(this); |
| |
| if (FAILED(hr)) |
| return hr; |
| |
| MFT_OUTPUT_STREAM_INFO& info = *pStreamInfo; |
| |
| // enum _MFT_OUTPUT_STREAM_INFO_FLAGS |
| // { MFT_OUTPUT_STREAM_WHOLE_SAMPLES = 0x1, |
| // MFT_OUTPUT_STREAM_SINGLE_SAMPLE_PER_BUFFER = 0x2, |
| // MFT_OUTPUT_STREAM_FIXED_SAMPLE_SIZE = 0x4, |
| // MFT_OUTPUT_STREAM_DISCARDABLE = 0x8, |
| // MFT_OUTPUT_STREAM_OPTIONAL = 0x10, |
| // MFT_OUTPUT_STREAM_PROVIDES_SAMPLES = 0x100, |
| // MFT_OUTPUT_STREAM_CAN_PROVIDE_SAMPLES = 0x200, |
| // MFT_OUTPUT_STREAM_LAZY_READ = 0x400, |
| // MFT_OUTPUT_STREAM_REMOVABLE = 0x800 |
| // }; |
| |
| // see Decoder sample in the SDK |
| // decoder.cc |
| |
| // The API says that the only flag that is meaningful prior to SetOutputType |
| // is the OPTIONAL flag. We need the frame dimensions, and the stride, |
| // in order to calculte the cbSize value. |
| |
| info.dwFlags = MFT_OUTPUT_STREAM_WHOLE_SAMPLES | |
| MFT_OUTPUT_STREAM_SINGLE_SAMPLE_PER_BUFFER | |
| MFT_OUTPUT_STREAM_FIXED_SAMPLE_SIZE; |
| |
| FrameSize size; |
| |
| info.cbSize = GetOutputBufferSize(size); |
| info.cbAlignment = 0; |
| |
| return S_OK; |
| } |
| |
| DWORD WebmMfVp8Dec::GetOutputBufferSize(FrameSize& s) const { |
| // MFT was already locked by caller |
| |
| // TODO: for now, assume width and height are specified |
| // via the input media type. |
| |
| if (m_pInputMediaType == 0) |
| return 0; |
| |
| HRESULT hr = MFGetAttributeSize(m_pInputMediaType, MF_MT_FRAME_SIZE, &s.width, |
| &s.height); |
| |
| assert(SUCCEEDED(hr)); |
| |
| const DWORD w = s.width; |
| assert(w); |
| |
| const DWORD h = s.height; |
| assert(h); |
| |
| const DWORD cb = w * h + 2 * (((w + 1) / 2) * ((h + 1) / 2)); |
| |
| // TODO: this result does not account for stride |
| |
| return cb; |
| } |
| |
| HRESULT WebmMfVp8Dec::GetAttributes(IMFAttributes** pp) { |
| if (pp) |
| *pp = 0; |
| |
| return E_NOTIMPL; |
| } |
| |
| HRESULT WebmMfVp8Dec::GetInputStreamAttributes(DWORD, IMFAttributes** pp) { |
| if (pp) |
| *pp = 0; |
| |
| return E_NOTIMPL; |
| } |
| |
| HRESULT WebmMfVp8Dec::GetOutputStreamAttributes(DWORD, IMFAttributes** pp) { |
| if (pp) |
| *pp = 0; |
| |
| return E_NOTIMPL; |
| } |
| |
| HRESULT WebmMfVp8Dec::DeleteInputStream(DWORD) { return E_NOTIMPL; } |
| |
| HRESULT WebmMfVp8Dec::AddInputStreams(DWORD, DWORD*) { return E_NOTIMPL; } |
| |
| HRESULT WebmMfVp8Dec::GetInputAvailableType(DWORD dwInputStreamID, |
| DWORD dwTypeIndex, |
| IMFMediaType** pp) { |
| if (pp) |
| *pp = 0; |
| |
| if (dwInputStreamID != 0) |
| return MF_E_INVALIDSTREAMNUMBER; |
| |
| if (dwTypeIndex >= kNumInputMediaSubtypes) |
| return MF_E_NO_MORE_TYPES; |
| |
| if (pp == 0) |
| return E_POINTER; |
| |
| IMFMediaType*& pmt = *pp; |
| |
| HRESULT hr = MFCreateMediaType(&pmt); |
| assert(SUCCEEDED(hr)); |
| assert(pmt); |
| |
| hr = pmt->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); |
| assert(SUCCEEDED(hr)); |
| |
| hr = pmt->SetGUID(MF_MT_SUBTYPE, kInputMediaSubtypes[dwTypeIndex]); |
| assert(SUCCEEDED(hr)); |
| |
| hr = pmt->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, FALSE); |
| assert(SUCCEEDED(hr)); |
| |
| hr = pmt->SetUINT32(MF_MT_COMPRESSED, TRUE); |
| assert(SUCCEEDED(hr)); |
| |
| return S_OK; |
| } |
| |
| HRESULT WebmMfVp8Dec::GetOutputAvailableType(DWORD dwOutputStreamID, |
| DWORD dwTypeIndex, |
| IMFMediaType** pp) { |
| if (pp) |
| *pp = 0; |
| |
| if (dwOutputStreamID != 0) |
| return MF_E_INVALIDSTREAMNUMBER; |
| |
| enum { subtype_count = 3 }; |
| |
| if (dwTypeIndex >= subtype_count) |
| return MF_E_NO_MORE_TYPES; |
| |
| if (pp == 0) |
| return E_POINTER; |
| |
| IMFMediaType*& pmt = *pp; |
| |
| Lock lock; |
| |
| HRESULT hr = lock.Seize(this); |
| |
| if (FAILED(hr)) |
| return hr; |
| |
| hr = MFCreateMediaType(&pmt); |
| assert(SUCCEEDED(hr)); |
| assert(pmt); |
| |
| hr = pmt->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); |
| assert(SUCCEEDED(hr)); |
| |
| const GUID subtypes[subtype_count] = {MFVideoFormat_NV12, MFVideoFormat_YV12, |
| MFVideoFormat_IYUV}; |
| |
| const GUID& subtype = subtypes[dwTypeIndex]; |
| |
| hr = pmt->SetGUID(MF_MT_SUBTYPE, subtype); |
| assert(SUCCEEDED(hr)); |
| |
| hr = pmt->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE); |
| assert(SUCCEEDED(hr)); |
| |
| hr = pmt->SetUINT32(MF_MT_COMPRESSED, FALSE); |
| assert(SUCCEEDED(hr)); |
| |
| if (m_pInputMediaType == 0) // nothing else we can say |
| return S_OK; |
| |
| FrameRate r; |
| |
| hr = r.AssignFrom(m_pInputMediaType); |
| |
| if (hr == S_OK) // means attribute was set, and its value is valid |
| { |
| // odbgstream os; |
| // os << "\nvp8dec: num=" << r.m_numerator |
| // << " den=" << r.m_denominator; |
| // |
| // const float rate = float(r.m_numerator) / float(r.m_denominator); |
| // os << rate << '\n' << endl; |
| |
| // TODO: a simpler way to handle this is to copy |
| // an item directly from media type to media type (assuming |
| // the MF API provides such a function), instead of going |
| // through this intermediate FrameRate value. |
| |
| hr = r.CopyTo(pmt); |
| assert(SUCCEEDED(hr)); |
| } |
| |
| FrameSize s; |
| |
| hr = MFGetAttributeSize(m_pInputMediaType, MF_MT_FRAME_SIZE, &s.width, |
| &s.height); |
| |
| assert(SUCCEEDED(hr)); |
| assert(s.width); |
| assert(s.height); |
| |
| hr = MFSetAttributeSize(pmt, MF_MT_FRAME_SIZE, s.width, s.height); |
| |
| assert(SUCCEEDED(hr)); |
| |
| return S_OK; |
| } |
| |
| HRESULT WebmMfVp8Dec::SetInputType(DWORD dwInputStreamID, IMFMediaType* pmt, |
| DWORD dwFlags) { |
| if (dwInputStreamID != 0) |
| return MF_E_INVALIDSTREAMNUMBER; |
| |
| Lock lock; |
| |
| HRESULT hr = lock.Seize(this); |
| |
| if (FAILED(hr)) |
| return hr; |
| |
| if (pmt == 0) { |
| // TODO: disallow this case while we're playing? |
| |
| if (m_pInputMediaType) { |
| const ULONG n = m_pInputMediaType->Release(); |
| n; |
| assert(n == 0); |
| |
| m_pInputMediaType = 0; |
| |
| const vpx_codec_err_t e = vpx_codec_destroy(&m_ctx); |
| e; |
| assert(e == VPX_CODEC_OK); |
| } |
| |
| if (m_pOutputMediaType) { |
| const ULONG n = m_pOutputMediaType->Release(); |
| n; |
| assert(n == 0); |
| |
| m_pOutputMediaType = 0; |
| } |
| |
| return S_OK; |
| } |
| |
| // TODO: handle the case when already have an input media type |
| // or output media type, or are already playing. I don't |
| // think we can change media types while we're playing. |
| |
| GUID g; |
| |
| hr = pmt->GetMajorType(&g); |
| |
| if (FAILED(hr)) |
| return MF_E_INVALIDMEDIATYPE; |
| |
| if (g != MFMediaType_Video) |
| return MF_E_INVALIDMEDIATYPE; |
| |
| hr = pmt->GetGUID(MF_MT_SUBTYPE, &g); |
| |
| if (FAILED(hr)) |
| return MF_E_INVALIDMEDIATYPE; |
| |
| if (g != WebmTypes::MEDIASUBTYPE_VP80 && g != WebmTypes::MEDIASUBTYPE_VP90) |
| return MF_E_INVALIDMEDIATYPE; |
| |
| // hr = pmt->SetUINT32(MF_MT_COMPRESSED, FALSE); |
| // assert(SUCCEEDED(hr)); |
| |
| FrameRate r; |
| |
| hr = r.AssignFrom(pmt); |
| |
| if (FAILED(hr)) // input mt has framerate, but it's bad |
| return hr; |
| |
| r.CopyTo(m_frame_rate); |
| |
| // odbgstream os; |
| // os << "\nvp8dec: framerate=" << m_frame_rate << '\n' << endl; |
| |
| FrameSize s; |
| |
| hr = MFGetAttributeSize(pmt, MF_MT_FRAME_SIZE, &s.width, &s.height); |
| |
| if (FAILED(hr)) |
| return MF_E_INVALIDMEDIATYPE; |
| |
| if (s.width == 0) |
| return MF_E_INVALIDMEDIATYPE; |
| |
| if (s.width % 2) // TODO |
| return MF_E_INVALIDMEDIATYPE; |
| |
| if (s.height == 0) |
| return MF_E_INVALIDMEDIATYPE; |
| |
| // TODO: do we need to check for odd height too? |
| |
| if (dwFlags & MFT_SET_TYPE_TEST_ONLY) |
| return S_OK; |
| |
| if (m_pInputMediaType) { |
| hr = m_pInputMediaType->DeleteAllItems(); |
| assert(SUCCEEDED(hr)); |
| |
| const vpx_codec_err_t e = vpx_codec_destroy(&m_ctx); |
| e; |
| assert(e == VPX_CODEC_OK); |
| } else { |
| hr = MFCreateMediaType(&m_pInputMediaType); |
| |
| if (FAILED(hr)) |
| return hr; |
| } |
| |
| hr = pmt->CopyAllItems(m_pInputMediaType); |
| |
| if (FAILED(hr)) |
| return hr; |
| |
| // m_frame_rate = r; |
| |
| // TODO: should this really be done here? |
| |
| vpx_codec_iface_t& vpx = (g == WebmTypes::MEDIASUBTYPE_VP80) ? |
| vpx_codec_vp8_dx_algo : vpx_codec_vp9_dx_algo; |
| |
| const int flags = 0; // TODO: VPX_CODEC_USE_POSTPROC; |
| |
| const vpx_codec_err_t err = vpx_codec_dec_init(&m_ctx, &vpx, 0, flags); |
| |
| if (err == VPX_CODEC_MEM_ERROR) |
| return E_OUTOFMEMORY; |
| |
| if (err != VPX_CODEC_OK) |
| return E_FAIL; |
| |
| // const HRESULT hr = OnApplyPostProcessing(); |
| |
| if (m_pOutputMediaType) { |
| // TODO: Is this the correct behavior? |
| |
| const ULONG n = m_pOutputMediaType->Release(); |
| n; |
| assert(n == 0); |
| |
| m_pOutputMediaType = 0; |
| } |
| |
| #if 0 |
| //TODO: |
| //We could update the preferred ("available") output media types, |
| //now that we know the frame rate and frame size, etc. |
| |
| hr = MFCreateMediaType(&m_pOutputMediaType); |
| assert(SUCCEEDED(hr)); //TODO |
| |
| hr = pmt->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); |
| assert(SUCCEEDED(hr)); //TODO |
| |
| hr = pmt->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_YV12); |
| assert(SUCCEEDED(hr)); //TODO |
| |
| hr = pmt->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE); |
| assert(SUCCEEDED(hr)); //TODO |
| |
| hr = pmt->SetUINT32(MF_MT_COMPRESSED, FALSE); |
| assert(SUCCEEDED(hr)); //TODO |
| |
| if (r.numerator) //means "has been set" |
| { |
| hr = MFSetAttributeRatio( |
| m_pOutputMediaType, |
| MF_MT_FRAME_RATE, |
| r.numerator, |
| r.denominator); |
| |
| assert(SUCCEEDED(hr)); //TODO |
| } |
| |
| hr = MFSetAttributeSize( |
| m_pOutputMediaType, |
| MF_MT_FRAME_SIZE, |
| s.width, |
| s.height); |
| |
| assert(SUCCEEDED(hr)); //TODO |
| #endif |
| |
| return S_OK; |
| } |
| |
| HRESULT WebmMfVp8Dec::SetOutputType(DWORD dwOutputStreamID, IMFMediaType* pmt, |
| DWORD dwFlags) { |
| if (dwOutputStreamID != 0) |
| return MF_E_INVALIDSTREAMNUMBER; |
| |
| Lock lock; |
| |
| HRESULT hr = lock.Seize(this); |
| |
| if (FAILED(hr)) |
| return hr; |
| |
| if (pmt == 0) { |
| // TODO: disallow this case while we're playing? |
| |
| if (m_pOutputMediaType) { |
| const ULONG n = m_pOutputMediaType->Release(); |
| n; |
| assert(n == 0); |
| |
| m_pOutputMediaType = 0; |
| } |
| |
| return S_OK; |
| } |
| |
| if (m_pInputMediaType == 0) |
| return MF_E_TRANSFORM_TYPE_NOT_SET; |
| |
| GUID g; |
| |
| hr = pmt->GetMajorType(&g); |
| |
| if (FAILED(hr)) |
| return MF_E_INVALIDMEDIATYPE; |
| |
| if (g != MFMediaType_Video) |
| return MF_E_INVALIDMEDIATYPE; |
| |
| hr = pmt->GetGUID(MF_MT_SUBTYPE, &g); |
| |
| if (FAILED(hr)) |
| return MF_E_INVALIDMEDIATYPE; |
| |
| if (g == MFVideoFormat_NV12) |
| __noop; |
| else if (g == MFVideoFormat_YV12) |
| __noop; |
| else if (g == MFVideoFormat_IYUV) |
| __noop; |
| else // TODO: add I420 support |
| return MF_E_INVALIDMEDIATYPE; |
| |
| // hr = pmt->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE); |
| // assert(SUCCEEDED(hr)); |
| |
| // hr = pmt->SetUINT32(MF_MT_COMPRESSED, FALSE); |
| // assert(SUCCEEDED(hr)); |
| |
| FrameRate r_out; |
| |
| hr = r_out.AssignFrom(pmt); |
| |
| if (FAILED(hr)) // output type has value, but it's not valid |
| return hr; |
| |
| if (hr == S_OK) // output type has value, and it's valid |
| { |
| FrameRate r_in; |
| |
| hr = r_in.AssignFrom(m_pInputMediaType); |
| |
| if ((hr == S_OK) && !r_in.Match(r_out)) |
| return MF_E_INVALIDMEDIATYPE; |
| } |
| |
| FrameSize s_out; |
| |
| hr = MFGetAttributeSize(pmt, MF_MT_FRAME_SIZE, &s_out.width, &s_out.height); |
| |
| if (SUCCEEDED(hr)) { |
| if (s_out.width == 0) |
| return MF_E_INVALIDMEDIATYPE; |
| |
| // TODO: check whether width is odd |
| |
| if (s_out.height == 0) |
| return MF_E_INVALIDMEDIATYPE; |
| |
| FrameSize s_in; |
| |
| hr = MFGetAttributeSize(m_pInputMediaType, MF_MT_FRAME_SIZE, &s_in.width, |
| &s_in.height); |
| |
| assert(SUCCEEDED(hr)); |
| |
| if (s_out.width != s_in.width) |
| return MF_E_INVALIDMEDIATYPE; |
| |
| if (s_out.height != s_in.height) |
| return MF_E_INVALIDMEDIATYPE; |
| } |
| |
| if (dwFlags & MFT_SET_TYPE_TEST_ONLY) |
| return S_OK; |
| |
| if (m_pOutputMediaType) { |
| hr = m_pOutputMediaType->DeleteAllItems(); |
| assert(SUCCEEDED(hr)); |
| } else { |
| hr = MFCreateMediaType(&m_pOutputMediaType); |
| |
| if (FAILED(hr)) |
| return hr; |
| } |
| |
| hr = pmt->CopyAllItems(m_pOutputMediaType); |
| |
| if (FAILED(hr)) |
| return hr; |
| |
| // TODO: do something |
| |
| return S_OK; |
| } |
| |
| HRESULT WebmMfVp8Dec::GetInputCurrentType(DWORD dwInputStreamID, |
| IMFMediaType** pp) { |
| if (dwInputStreamID != 0) |
| return MF_E_INVALIDSTREAMNUMBER; |
| |
| if (pp) |
| *pp = 0; |
| |
| Lock lock; |
| |
| HRESULT hr = lock.Seize(this); |
| |
| if (FAILED(hr)) |
| return hr; |
| |
| if (m_pInputMediaType == 0) |
| return MF_E_TRANSFORM_TYPE_NOT_SET; |
| |
| IMFMediaType*& p = *pp; |
| |
| hr = MFCreateMediaType(&p); |
| |
| if (FAILED(hr)) |
| return hr; |
| |
| return m_pInputMediaType->CopyAllItems(p); |
| } |
| |
| HRESULT WebmMfVp8Dec::GetOutputCurrentType(DWORD dwOutputStreamID, |
| IMFMediaType** pp) { |
| if (dwOutputStreamID != 0) |
| return MF_E_INVALIDSTREAMNUMBER; |
| |
| if (pp) |
| *pp = 0; |
| |
| Lock lock; |
| |
| HRESULT hr = lock.Seize(this); |
| |
| if (FAILED(hr)) |
| return hr; |
| |
| // TODO: synthesize from input media type? |
| |
| if (m_pOutputMediaType == 0) // TODO: liberalize? |
| return MF_E_TRANSFORM_TYPE_NOT_SET; |
| |
| IMFMediaType*& p = *pp; |
| |
| hr = MFCreateMediaType(&p); |
| |
| if (FAILED(hr)) |
| return hr; |
| |
| return m_pOutputMediaType->CopyAllItems(p); |
| } |
| |
| HRESULT WebmMfVp8Dec::GetInputStatus(DWORD dwInputStreamID, DWORD* pdwFlags) { |
| if (dwInputStreamID != 0) |
| return MF_E_INVALIDSTREAMNUMBER; |
| |
| Lock lock; |
| |
| HRESULT hr = lock.Seize(this); |
| |
| if (FAILED(hr)) |
| return hr; |
| |
| if (m_pInputMediaType == 0) |
| return MF_E_TRANSFORM_TYPE_NOT_SET; |
| |
| // TODO: check output media type too? |
| |
| if (pdwFlags == 0) |
| return E_POINTER; |
| |
| DWORD& dwFlags = *pdwFlags; |
| |
| dwFlags = MFT_INPUT_STATUS_ACCEPT_DATA; // because we always queue |
| |
| return S_OK; |
| } |
| |
| HRESULT WebmMfVp8Dec::GetOutputStatus(DWORD*) { |
| #if 1 |
| return E_NOTIMPL; |
| #else |
| Lock lock; |
| |
| HRESULT hr = lock.Seize(this); |
| |
| if (FAILED(hr)) |
| return hr; |
| |
| if (m_pInputMediaType == 0) |
| return MF_E_TRANSFORM_TYPE_NOT_SET; |
| |
| // TODO: check output media type too? |
| |
| if (pdwFlags == 0) |
| return E_POINTER; |
| |
| DWORD& dwFlags = *pdwFlags; |
| |
| const vpx_image_t* const f = vpx_codec_get_frame(&m_ctx, &m_iter); |
| |
| dwFlags = f ? MFT_OUTPUT_STATUS_SAMPLE_READY : 0; |
| |
| // TODO: alternatively, we could return E_NOTIMPL, which |
| // forces client to call ProcessOutput to determine whether |
| // a sample is ready. |
| |
| return S_OK; |
| #endif |
| } |
| |
| HRESULT WebmMfVp8Dec::SetOutputBounds(LONGLONG /* hnsLowerBound */, |
| LONGLONG /* hnsUpperBound */) { |
| return E_NOTIMPL; // TODO |
| } |
| |
| HRESULT WebmMfVp8Dec::ProcessEvent(DWORD dwInputStreamID, |
| IMFMediaEvent* /* pEvent */) { |
| if (dwInputStreamID != 0) |
| return MF_E_INVALIDSTREAMNUMBER; |
| |
| #if 0 // def _DEBUG |
| if (pEvent) |
| { |
| MediaEventType t; |
| |
| HRESULT hr = pEvent->GetType(&t); |
| assert(SUCCEEDED(hr)); |
| |
| odbgstream os; |
| os << "WebmMfVp8Dec::ProcessEvent: type=" << t << endl; |
| } |
| #endif |
| |
| return E_NOTIMPL; // TODO |
| } |
| |
| HRESULT WebmMfVp8Dec::ProcessMessage(MFT_MESSAGE_TYPE m, ULONG_PTR) { |
| #if 0 // def _DEBUG |
| odbgstream os; |
| os << "WebmMfVp8Dec::ProcessMessage(samples.size=" |
| << m_samples.size() << "): "; |
| #endif |
| |
| switch (m) { |
| case MFT_MESSAGE_COMMAND_FLUSH: |
| #if 0 // def _DEBUG |
| os << "COMMAND_FLUSH" << endl; |
| #endif |
| |
| // http://msdn.microsoft.com/en-us/library/dd940419%28v=VS.85%29.aspx |
| |
| Flush(); |
| return S_OK; |
| |
| case MFT_MESSAGE_COMMAND_DRAIN: |
| #if 0 // def _DEBUG |
| os << "COMMAND_DRAIN" << endl; |
| #endif |
| |
| // http://msdn.microsoft.com/en-us/library/dd940418%28v=VS.85%29.aspx |
| |
| // TODO: input stream does not accept input in the MFT processes all |
| // data from previous calls to ProcessInput. |
| |
| return S_OK; |
| |
| case MFT_MESSAGE_SET_D3D_MANAGER: |
| #if 0 // def _DEBUG |
| os << "SET_D3D" << endl; |
| #endif |
| |
| return S_OK; |
| |
| case MFT_MESSAGE_DROP_SAMPLES: |
| #if 0 // def _DEBUG |
| os << "DROP_SAMPLES" << endl; |
| #endif |
| |
| return S_OK; |
| |
| case MFT_MESSAGE_NOTIFY_BEGIN_STREAMING: |
| #if 0 // def _DEBUG |
| os << "NOTIFY_BEGIN_STREAMING" << endl; |
| #endif |
| |
| // http://msdn.microsoft.com/en-us/library/dd940421%28v=VS.85%29.aspx |
| |
| // TODO: init decoder library here, instead of during SetInputType |
| |
| return S_OK; |
| |
| case MFT_MESSAGE_NOTIFY_END_STREAMING: |
| #if 0 // def _DEBUG |
| os << "NOTIFY_END_STREAMING" << endl; |
| #endif |
| |
| // http://msdn.microsoft.com/en-us/library/dd940423%28v=VS.85%29.aspx |
| |
| // NOTE: flush is not performed here |
| |
| return S_OK; |
| |
| case MFT_MESSAGE_NOTIFY_END_OF_STREAM: |
| #if 0 // def _DEBUG |
| os << "NOTIFY_EOS" << endl; |
| #endif |
| |
| // http://msdn.microsoft.com/en-us/library/dd940422%28v=VS.85%29.aspx |
| |
| // TODO: set discontinuity flag on first sample after this |
| |
| return S_OK; |
| |
| case MFT_MESSAGE_NOTIFY_START_OF_STREAM: |
| #if 0 // def _DEBUG |
| os << "NOTIFY_START_OF_STREAM" << endl; |
| #endif |
| |
| return S_OK; |
| |
| case MFT_MESSAGE_COMMAND_MARKER: |
| #if 0 // def _DEBUG |
| os << "COMMAND_MARKER" << endl; |
| #endif |
| |
| return S_OK; |
| |
| default: |
| return S_OK; |
| } |
| } |
| |
| HRESULT WebmMfVp8Dec::ProcessInput(DWORD dwInputStreamID, IMFSample* pSample, |
| DWORD) { |
| if (dwInputStreamID != 0) |
| return MF_E_INVALIDSTREAMNUMBER; |
| |
| if (pSample == 0) |
| return E_INVALIDARG; |
| |
| DWORD count; |
| |
| HRESULT hr = pSample->GetBufferCount(&count); |
| assert(SUCCEEDED(hr)); |
| |
| if (count == 0) // weird |
| return S_OK; |
| |
| // if (count > 1) |
| // return E_INVALIDARG; |
| |
| // TODO: check duration |
| // TODO: check timestamp |
| |
| Lock lock; |
| |
| hr = lock.Seize(this); |
| |
| if (FAILED(hr)) |
| return hr; |
| |
| if (m_pInputMediaType == 0) |
| return MF_E_TRANSFORM_TYPE_NOT_SET; |
| |
| if (m_pOutputMediaType == 0) // TODO: need this check here? |
| return MF_E_TRANSFORM_TYPE_NOT_SET; |
| |
| pSample->AddRef(); |
| |
| m_samples.push_back(SampleInfo()); |
| |
| SampleInfo& i = m_samples.back(); |
| |
| i.pSample = pSample; |
| i.dwBuffer = 0; |
| |
| return S_OK; |
| } |
| |
| HRESULT WebmMfVp8Dec::ProcessOutput(DWORD dwFlags, DWORD cOutputBufferCount, |
| MFT_OUTPUT_DATA_BUFFER* pOutputSamples, |
| DWORD* pdwStatus) { |
| if (pdwStatus) |
| *pdwStatus = 0; |
| |
| if (dwFlags) |
| return E_INVALIDARG; |
| |
| Lock lock; |
| |
| HRESULT hr = lock.Seize(this); |
| |
| if (FAILED(hr)) |
| return hr; |
| |
| if (m_pInputMediaType == 0) |
| return MF_E_TRANSFORM_TYPE_NOT_SET; |
| |
| if (m_pOutputMediaType == 0) |
| return MF_E_TRANSFORM_TYPE_NOT_SET; |
| |
| if (cOutputBufferCount == 0) |
| return E_INVALIDARG; |
| |
| if (pOutputSamples == 0) |
| return E_INVALIDARG; |
| |
| const MFT_OUTPUT_DATA_BUFFER& data = pOutputSamples[0]; |
| // data.dwStreamID should equal 0, but we ignore it |
| |
| for (;;) { |
| hr = Decode(data.pSample); |
| |
| if (FAILED(hr) || (hr == S_OK)) |
| return hr; |
| } |
| } |
| |
| HRESULT WebmMfVp8Dec::Decode(IMFSample* pSample_out) { |
| if (pSample_out == 0) |
| return E_INVALIDARG; |
| |
| if (m_samples.empty()) |
| return MF_E_TRANSFORM_NEED_MORE_INPUT; |
| |
| HRESULT hr; |
| LONGLONG time, duration; |
| |
| { |
| SampleInfo& i = m_samples.front(); |
| assert(i.pSample); |
| |
| UINT32 iKey; |
| |
| hr = i.pSample->GetUINT32(MFSampleExtension_CleanPoint, &iKey); |
| |
| const bool bKey = SUCCEEDED(hr) && (iKey != FALSE); |
| |
| // TODO: this is an optimization oppurtunity, since it would |
| // optimize-away the expense of decoding too (not the expense |
| // of mere rendering, which is what other modes optimize away). |
| // It's tricky to implement since if we |
| // transition OUT of DROP_5 mode, then it's possible that we |
| // we're in the middle of a GOP and so we cannot decode |
| // again until we see the next keyframe. In other modes, we |
| // decode every frame (we are suppressing the rendering, not |
| // decode), so a state transition between modes is not an issue. |
| // For now, just decompress every frame. |
| // |
| // if (!bKey && (m_drop_mode == MF_DROP_MODE_5)) |
| //{ |
| // i.pSample->Release(); |
| // i.pSample = 0; |
| // |
| // m_samples.pop_front(); |
| // |
| // return S_FALSE; |
| //} |
| // END TODO. |
| |
| UINT32 bPreroll; |
| |
| hr = i.pSample->GetUINT32(WebmTypes::WebMSample_Preroll, &bPreroll); |
| |
| if (SUCCEEDED(hr) && (bPreroll != FALSE)) { |
| // preroll frame |
| hr = i.DecodeAll(m_ctx); |
| |
| i.pSample->Release(); |
| i.pSample = 0; |
| |
| m_samples.pop_front(); |
| |
| if (FAILED(hr)) // bad decode |
| return hr; |
| |
| ReplenishDropBudget(); |
| return S_FALSE; // throw away this sample |
| } |
| |
| hr = i.DecodeOne(m_ctx, time, duration); |
| |
| if (FAILED(hr) || (hr != S_OK)) { |
| i.pSample->Release(); |
| i.pSample = 0; |
| |
| m_samples.pop_front(); |
| |
| if (FAILED(hr)) // bad decode |
| return hr; |
| } |
| |
| if (m_drop_mode == MF_DROP_MODE_NONE) |
| __noop; // we're not throwing away frames |
| |
| else if (m_drop_mode >= MF_DROP_MODE_5) { |
| if (!bKey) |
| return S_FALSE; // throw away this sample |
| } else if (bKey || (m_drop_budget > 1)) |
| --m_drop_budget; // spend out budget to render this frame |
| |
| else { |
| ReplenishDropBudget(); |
| return S_FALSE; // throw away this sample |
| } |
| } |
| |
| DWORD count_out; |
| |
| hr = pSample_out->GetBufferCount(&count_out); |
| |
| if (SUCCEEDED(hr) && (count_out != 1)) // TODO: handle this? |
| return E_INVALIDARG; |
| |
| IMFMediaBufferPtr buf_out; |
| |
| hr = pSample_out->GetBufferByIndex(0, &buf_out); |
| |
| if (FAILED(hr) || !bool(buf_out)) |
| return E_INVALIDARG; |
| |
| GUID subtype; |
| |
| hr = m_pOutputMediaType->GetGUID(MF_MT_SUBTYPE, &subtype); |
| assert(SUCCEEDED(hr)); |
| |
| // TODO: |
| // MFVideoFormat_I420 |
| |
| FrameSize frame_size; |
| |
| const DWORD cbFrameLen = GetOutputBufferSize(frame_size); |
| assert(cbFrameLen > 0); |
| |
| // The sequence of querying for the output buffer is described on |
| // the page "Uncompressed Video Buffers". |
| // http://msdn.microsoft.com/en-us/library/aa473821%28v=VS.85%29.aspx |
| |
| IMF2DBuffer* buf2d_out; |
| |
| hr = buf_out->QueryInterface(&buf2d_out); |
| |
| if (SUCCEEDED(hr)) { |
| assert(buf2d_out); |
| |
| BYTE* ptr_out; |
| LONG stride_out; |
| |
| hr = buf2d_out->Lock2D(&ptr_out, &stride_out); |
| assert(SUCCEEDED(hr)); |
| assert(ptr_out); |
| assert(stride_out > 0); // top-down DIBs are positive, right? |
| |
| const HRESULT hrGetFrame = GetFrame(ptr_out, stride_out, subtype); |
| |
| // TODO: set output buffer length? |
| |
| hr = buf2d_out->Unlock2D(); |
| assert(SUCCEEDED(hr)); |
| |
| buf2d_out->Release(); |
| buf2d_out = 0; |
| |
| if (FAILED(hrGetFrame) || (hrGetFrame != S_OK)) |
| return hrGetFrame; |
| } else { |
| BYTE* ptr_out; |
| DWORD cbMaxLen; |
| |
| hr = buf_out->Lock(&ptr_out, &cbMaxLen, 0); |
| assert(SUCCEEDED(hr)); |
| assert(ptr_out); |
| assert(cbMaxLen >= cbFrameLen); |
| |
| // TODO: verify stride of output buffer |
| // The page "Uncompressed Video Buffers" here: |
| // http://msdn.microsoft.com/en-us/library/aa473821%28v=VS.85%29.aspx |
| // explains how to calculate the "minimum stride": |
| // MF_MT_DEFAULT_STRIDE |
| // or, MFGetStrideForBitmapInfoHeader |
| // or, calculate it yourself |
| |
| INT32 stride_out; |
| |
| hr = pSample_out->GetUINT32(MF_MT_DEFAULT_STRIDE, (UINT32*)&stride_out); |
| |
| if (SUCCEEDED(hr) && (stride_out != 0)) |
| assert(stride_out > 0); |
| else { |
| const DWORD fcc = subtype.Data1; |
| |
| const DWORD w = frame_size.width; |
| LONG stride_out_; |
| |
| hr = MFGetStrideForBitmapInfoHeader(fcc, w, &stride_out_); |
| |
| if (SUCCEEDED(hr) && (stride_out_ != 0)) { |
| assert(stride_out_ > 0); |
| stride_out = stride_out_; |
| } else { |
| assert((w % 2) == 0); // TODO |
| stride_out = w; // TODO: is this correct??? |
| } |
| } |
| |
| const HRESULT hrGetFrame = GetFrame(ptr_out, stride_out, subtype); |
| |
| hr = buf_out->Unlock(); |
| assert(SUCCEEDED(hr)); |
| |
| if (FAILED(hrGetFrame) || (hrGetFrame != S_OK)) |
| return hrGetFrame; |
| |
| hr = buf_out->SetCurrentLength(cbFrameLen); |
| assert(SUCCEEDED(hr)); |
| } |
| |
| if (time >= 0) { |
| hr = pSample_out->SetSampleTime(time); |
| assert(SUCCEEDED(hr)); |
| } |
| |
| if (duration >= 0) { |
| hr = pSample_out->SetSampleDuration(duration); |
| assert(SUCCEEDED(hr)); |
| } |
| |
| return S_OK; // done |
| } |
| |
| HRESULT WebmMfVp8Dec::GetFrame(BYTE* pOutBuf, ULONG strideOut, |
| const GUID& subtype) { |
| assert(pOutBuf); |
| assert(strideOut); |
| assert((strideOut % 2) == 0); // TODO: resolve this issue |
| |
| vpx_codec_iter_t iter = 0; |
| |
| const vpx_image_t* f = vpx_codec_get_frame(&m_ctx, &iter); |
| |
| if (f == 0) // alt-ref |
| return S_FALSE; // tell caller to pop this buffer and call me back |
| |
| // Scale (if necessary). |
| FrameSize size; |
| GetOutputBufferSize(size); |
| if (f->d_h != size.height || f->d_w != size.width) { |
| if (!webmdshow::LibyuvScaleI420(size.width, size.height, |
| f, &m_scaled_image)) { |
| assert(false && "webmdshow::LibyuvScaleI420 failed"); |
| return E_FAIL; |
| } |
| f = m_scaled_image; |
| } |
| |
| // Y |
| |
| const BYTE* pInY = f->planes[VPX_PLANE_Y]; |
| assert(pInY); |
| |
| unsigned int wIn = f->d_w; |
| unsigned int hIn = f->d_h; |
| |
| BYTE* pOut = pOutBuf; |
| |
| const int strideInY = f->stride[VPX_PLANE_Y]; |
| |
| for (unsigned int y = 0; y < hIn; ++y) { |
| memcpy(pOut, pInY, wIn); |
| pInY += strideInY; |
| pOut += strideOut; |
| } |
| |
| const BYTE* pInV = f->planes[VPX_PLANE_V]; |
| assert(pInV); |
| |
| const int strideInV = f->stride[VPX_PLANE_V]; |
| |
| const BYTE* pInU = f->planes[VPX_PLANE_U]; |
| assert(pInU); |
| |
| const int strideInU = f->stride[VPX_PLANE_U]; |
| |
| wIn = (wIn + 1) / 2; |
| hIn = (hIn + 1) / 2; |
| |
| if (subtype == MFVideoFormat_NV12) { |
| for (unsigned int y = 0; y < hIn; ++y) { |
| const BYTE* u = pInU; // src |
| const BYTE* v = pInV; // src |
| BYTE* uv = pOut; // dst |
| |
| for (unsigned int idx = 0; idx < wIn; ++idx) // uv |
| { |
| *uv++ = *u++; // Cb |
| *uv++ = *v++; // Cr |
| } |
| |
| pInU += strideInU; |
| pInV += strideInV; |
| pOut += strideOut; |
| } |
| } else if (subtype == MFVideoFormat_YV12) { |
| strideOut /= 2; |
| |
| // V |
| |
| for (unsigned int y = 0; y < hIn; ++y) { |
| memcpy(pOut, pInV, wIn); |
| pInV += strideInV; |
| pOut += strideOut; |
| } |
| |
| // U |
| |
| for (unsigned int y = 0; y < hIn; ++y) { |
| memcpy(pOut, pInU, wIn); |
| pInU += strideInU; |
| pOut += strideOut; |
| } |
| } else { |
| assert(subtype == MFVideoFormat_IYUV); |
| |
| strideOut /= 2; |
| |
| // U |
| |
| for (unsigned int y = 0; y < hIn; ++y) { |
| memcpy(pOut, pInU, wIn); |
| pInU += strideInU; |
| pOut += strideOut; |
| } |
| |
| // V |
| |
| for (unsigned int y = 0; y < hIn; ++y) { |
| memcpy(pOut, pInV, wIn); |
| pInV += strideInV; |
| pOut += strideOut; |
| } |
| } |
| |
| const vpx_image_t* const f2 = vpx_codec_get_frame(&m_ctx, &iter); |
| f2; |
| assert(f2 == 0); |
| |
| return S_OK; |
| } |
| |
| HRESULT WebmMfVp8Dec::SetRate(BOOL bThin, float rate) { |
| // odbgstream os; |
| // os << "WebmMfVp8Dec::SetRate: bThin=" |
| // << boolalpha << (bThin ? true : false) |
| // << " rate=" |
| // << rate |
| // << endl; |
| |
| Lock lock; |
| |
| HRESULT hr = lock.Seize(this); |
| |
| if (FAILED(hr)) |
| return hr; |
| |
| // if (m_pEvents == 0) |
| // return MF_E_SHUTDOWN; |
| |
| // if (bThin) |
| // return MF_E_THINNING_UNSUPPORTED; //TODO |
| |
| if (rate < 0) |
| return MF_E_REVERSE_UNSUPPORTED; // TODO |
| |
| m_rate = rate; |
| m_bThin = bThin; |
| |
| // reset drop mode when rate change |
| |
| m_lag_sum = 0; |
| m_lag_count = 0; |
| m_lag_frames = 0; |
| |
| hr = WebmMfVp8Dec::SetDropMode(MF_DROP_MODE_NONE); |
| assert(SUCCEEDED(hr)); |
| |
| return S_OK; |
| } |
| |
| HRESULT WebmMfVp8Dec::GetRate(BOOL* pbThin, float* pRate) { |
| Lock lock; |
| |
| HRESULT hr = lock.Seize(this); |
| |
| if (FAILED(hr)) |
| return hr; |
| |
| // if (m_pEvents == 0) |
| // return MF_E_SHUTDOWN; |
| |
| if (pbThin) |
| *pbThin = m_bThin; |
| |
| if (pRate) // return error when pRate ptr is NULL? |
| *pRate = m_rate; |
| |
| // odbgstream os; |
| // os << "WebmMfVp8Dec::GetRate: bThin=" |
| // << boolalpha << m_bThin |
| // << " rate=" |
| // << m_rate |
| // << endl; |
| |
| return S_OK; |
| } |
| |
| HRESULT WebmMfVp8Dec::GetSlowestRate(MFRATE_DIRECTION d, BOOL /* bThin */, |
| float* pRate) { |
| // odbgstream os; |
| // os << "WebmMfVp8Dec::GetSlowestRate" << endl; |
| |
| Lock lock; |
| |
| HRESULT hr = lock.Seize(this); |
| |
| if (FAILED(hr)) |
| return hr; |
| |
| // if (m_pEvents == 0) |
| // return MF_E_SHUTDOWN; |
| |
| if (d == MFRATE_REVERSE) |
| return MF_E_REVERSE_UNSUPPORTED; // TODO |
| |
| // if (bThin) |
| // return MF_E_THINNING_UNSUPPORTED; //TODO |
| |
| if (pRate == 0) |
| return E_POINTER; |
| |
| float& r = *pRate; |
| r = 0; //? |
| |
| return S_OK; |
| } |
| |
| HRESULT WebmMfVp8Dec::GetFastestRate(MFRATE_DIRECTION d, BOOL /* bThin */, |
| float* pRate) { |
| // odbgstream os; |
| // os << "WebmMfSource::GetFastestRate" << endl; |
| |
| Lock lock; |
| |
| HRESULT hr = lock.Seize(this); |
| |
| if (FAILED(hr)) |
| return hr; |
| |
| // if (m_pEvents == 0) |
| // return MF_E_SHUTDOWN; |
| |
| if (d == MFRATE_REVERSE) |
| return MF_E_REVERSE_UNSUPPORTED; // TODO |
| |
| // if (bThin) |
| // return MF_E_THINNING_UNSUPPORTED; //TODO |
| |
| if (pRate == 0) |
| return E_POINTER; |
| |
| float& r = *pRate; |
| r = 64; //? |
| |
| return S_OK; |
| } |
| |
| HRESULT WebmMfVp8Dec::IsRateSupported(BOOL /* bThin */, float rate, |
| float* pNearestRate) { |
| // odbgstream os; |
| // os << "WebmMfVp8Dec::IsRateSupported: rate=" << rate << endl; |
| |
| Lock lock; |
| |
| HRESULT hr = lock.Seize(this); |
| |
| if (FAILED(hr)) |
| return hr; |
| |
| // if (m_pEvents == 0) |
| // return MF_E_SHUTDOWN; |
| |
| // if (bThin) |
| // return MF_E_THINNING_UNSUPPORTED; //TODO |
| |
| if (rate < 0) |
| return MF_E_REVERSE_UNSUPPORTED; // TODO |
| |
| // float int_part; |
| // const float frac_part = modf(rate, &int_part); |
| |
| if (pNearestRate) |
| *pNearestRate = rate; |
| |
| return S_OK; // TODO |
| } |
| |
| HRESULT WebmMfVp8Dec::GetService(REFGUID sid, REFIID iid, LPVOID* ppv) { |
| if (sid == MF_RATE_CONTROL_SERVICE) |
| return WebmMfVp8Dec::QueryInterface(iid, ppv); |
| |
| if (ppv) |
| *ppv = 0; |
| |
| return MF_E_UNSUPPORTED_SERVICE; |
| } |
| |
| void WebmMfVp8Dec::Flush() { |
| while (!m_samples.empty()) { |
| SampleInfo& i = m_samples.front(); |
| assert(i.pSample); |
| |
| i.pSample->Release(); |
| i.pSample = 0; |
| |
| m_samples.pop_front(); |
| } |
| } |
| |
| HRESULT WebmMfVp8Dec::SampleInfo::DecodeAll(vpx_codec_ctx_t& ctx) { |
| assert(pSample); |
| |
| DWORD count; |
| |
| HRESULT hr = pSample->GetBufferCount(&count); |
| assert(SUCCEEDED(hr)); |
| assert(count > 0); |
| |
| while (dwBuffer < count) { |
| IMFMediaBufferPtr buf; |
| |
| hr = pSample->GetBufferByIndex(dwBuffer, &buf); |
| assert(SUCCEEDED(hr)); |
| assert(buf); |
| |
| BYTE* ptr; |
| DWORD len; |
| |
| hr = buf->Lock(&ptr, 0, &len); |
| assert(SUCCEEDED(hr)); |
| assert(ptr); |
| assert(len); |
| |
| const vpx_codec_err_t e = vpx_codec_decode(&ctx, ptr, len, 0, 0); |
| |
| hr = buf->Unlock(); |
| assert(SUCCEEDED(hr)); |
| |
| if (e != VPX_CODEC_OK) |
| return MF_E_INVALID_STREAM_DATA; |
| |
| ++dwBuffer; // consume this buffer |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT WebmMfVp8Dec::SampleInfo::DecodeOne(vpx_codec_ctx_t& ctx, |
| LONGLONG& time, |
| LONGLONG& duration) { |
| assert(pSample); |
| |
| DWORD count; |
| |
| HRESULT hr = pSample->GetBufferCount(&count); |
| assert(SUCCEEDED(hr)); |
| assert(count > 0); |
| assert(count > dwBuffer); |
| |
| IMFMediaBufferPtr buf; |
| |
| hr = pSample->GetBufferByIndex(dwBuffer, &buf); |
| assert(SUCCEEDED(hr)); |
| assert(buf); |
| |
| BYTE* ptr; |
| DWORD len; |
| |
| hr = buf->Lock(&ptr, 0, &len); |
| assert(SUCCEEDED(hr)); |
| assert(ptr); |
| assert(len); |
| |
| const vpx_codec_err_t e = vpx_codec_decode(&ctx, ptr, len, 0, 0); |
| |
| hr = buf->Unlock(); |
| assert(SUCCEEDED(hr)); |
| |
| if (e != VPX_CODEC_OK) |
| return MF_E_INVALID_STREAM_DATA; |
| |
| LONGLONG t; |
| |
| hr = pSample->GetSampleTime(&t); |
| |
| if (FAILED(hr) || (t < 0)) { |
| time = -1; |
| duration = -1; |
| } else { |
| LONGLONG d; |
| |
| hr = pSample->GetSampleDuration(&d); |
| |
| if (FAILED(hr) || (d <= 0)) { |
| if (dwBuffer == 0) // first buffer |
| time = t; |
| else |
| time = -1; |
| |
| duration = -1; |
| } else { |
| duration = d / count; |
| time = t + dwBuffer * duration; |
| } |
| } |
| |
| ++dwBuffer; // consume this buffer |
| return (dwBuffer >= count) ? S_FALSE : S_OK; |
| } |
| |
| HRESULT WebmMfVp8Dec::SetDropMode(MF_QUALITY_DROP_MODE d) { |
| if (d > MF_DROP_MODE_5) |
| return MF_E_NO_MORE_DROP_MODES; |
| |
| Lock lock; |
| |
| HRESULT hr = lock.Seize(this); |
| |
| if (FAILED(hr)) |
| return hr; |
| |
| m_drop_mode = d; |
| m_drop_budget = 0; |
| |
| // odbgstream os; |
| // os << "SetDropMode=" << m_drop_mode << endl; |
| |
| return S_OK; |
| |
| // we always keep keyframes ("cleanpoint") |
| // 1: drop 1 out of every 2 frames |
| // 2: drop 2 out of every 3 frames |
| // 3: drop 3 out of every 4 frames |
| // 4: drop 4 out of every 5 frames |
| // 5: drop all delta frames |
| // |
| // alternatively: |
| // 1: drop 1 out of every 16 = 2^4 = 2^(5-1) |
| // 2: drop 1 out of every 8 = 2^3 = 2^(5-2) = 2 out of every 16 |
| // 3: drop 1 out of every 4 = 2^2 = 2^(5-3) = 4 out of every 16 |
| // 4: drop 1 out of every 2 = 2^1 = 2^(5-4) = 8 out of every 16 |
| // 5: drop all delta frames |
| |
| // when we detect a keyframe, this resets the counter |
| // |
| // level 1: |
| // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 |
| // x |
| // |
| // level 2: |
| // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 |
| // x x |
| // |
| // level 3: |
| // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 |
| // x x x x x |
| // |
| // level 4: |
| // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 |
| // x x x x x x x x x x |
| // |
| // we could also tie this to framerate, e.g. |
| // 1: drop 1 frame per second |
| // 2: drop 1 frame per 1/2 sec |
| // 3: drop 1 frame per 1/4 sec |
| // 4: drop 1 frame per 1/8 sec |
| // 5: drop all delta frames |
| // |
| // Give outselves a budget: the smaller the drop mode, |
| // the larger the budget. Drop the first eligrable |
| // frame when our budget drops to 0. |
| } |
| |
| void WebmMfVp8Dec::ReplenishDropBudget() { |
| if (m_drop_mode == MF_DROP_MODE_NONE) { |
| m_drop_budget = 0; // doesn't really matter |
| return; |
| } |
| |
| if (m_drop_mode >= MF_DROP_MODE_5) { |
| m_drop_budget = 0; // doesn't really matter |
| return; |
| } |
| |
| const int d = m_drop_mode; |
| assert(d >= 1); |
| assert(d <= 4); |
| |
| m_drop_budget = (1 << (5 - d)); |
| } |
| |
| HRESULT WebmMfVp8Dec::SetQualityLevel(MF_QUALITY_LEVEL q) { |
| if (q == MF_QUALITY_NORMAL) |
| return S_OK; |
| |
| return MF_E_NO_MORE_QUALITY_LEVELS; |
| } |
| |
| HRESULT WebmMfVp8Dec::GetDropMode(MF_QUALITY_DROP_MODE* p) { |
| if (p == 0) |
| return E_POINTER; |
| |
| Lock lock; |
| |
| HRESULT hr = lock.Seize(this); |
| |
| if (FAILED(hr)) |
| return hr; |
| |
| MF_QUALITY_DROP_MODE& d = *p; |
| d = m_drop_mode; |
| |
| // odbgstream os; |
| // os << "GetDropMode=" << m_drop_mode << endl; |
| |
| return S_OK; |
| } |
| |
| HRESULT WebmMfVp8Dec::GetQualityLevel(MF_QUALITY_LEVEL* p) { |
| if (p == 0) |
| return E_POINTER; |
| |
| MF_QUALITY_LEVEL& q = *p; |
| q = MF_QUALITY_NORMAL; // no post-processing for us to shut off |
| |
| return S_OK; |
| } |
| |
| HRESULT WebmMfVp8Dec::DropTime(LONGLONG t) { |
| t; |
| return MF_E_DROPTIME_NOT_SUPPORTED; |
| } |
| |
| HRESULT WebmMfVp8Dec::NotifyQualityEvent(IMFMediaEvent* pe, DWORD* pdw) { |
| if (pe == 0) |
| return E_INVALIDARG; |
| |
| if (pdw == 0) |
| return E_POINTER; |
| |
| DWORD& dw = *pdw; |
| dw = 0; // TODO: possibly set this to MF_QUALITY_CANNOT_KEEP_UP |
| |
| MediaEventType type; |
| |
| HRESULT hr = pe->GetType(&type); |
| |
| if (FAILED(hr)) |
| return hr; |
| |
| if (type != MEQualityNotify) |
| return E_INVALIDARG; |
| |
| GUID ext; |
| |
| hr = pe->GetExtendedType(&ext); |
| |
| if (FAILED(hr)) |
| return hr; |
| |
| if (ext != MF_QUALITY_NOTIFY_SAMPLE_LAG) |
| return S_OK; |
| |
| PROPVARIANT v; |
| |
| hr = pe->GetValue(&v); |
| |
| if (FAILED(hr)) |
| return E_INVALIDARG; |
| |
| if (v.vt != VT_I8) |
| return E_INVALIDARG; |
| |
| const LONGLONG reftime = v.hVal.QuadPart; |
| |
| // MEQualityNotify Event |
| // http://msdn.microsoft.com/en-us/library/ms694893(VS.85).aspx |
| |
| // negative is good - frames are early |
| // positive is bad - frames are late |
| // value is a lateness - time in 100ns units |
| // we're seeing extreme lateness: 2 or 3s |
| |
| // if (ext == MF_QUALITY_NOTIFY_PROCESSING_LATENCY) |
| // return S_OK; |
| |
| Lock lock; |
| |
| hr = lock.Seize(this); |
| |
| if (FAILED(hr)) |
| return hr; |
| |
| if (m_frame_rate <= 0) // no framerate available |
| return S_OK; // TODO: for now, don't bother trying to drop |
| |
| if (m_bThin || (m_rate != 1)) // bThin = true implies drop level 5 already |
| return S_OK; |
| |
| if (reftime <= 0) // assume we're all caught up, even if value is spurious |
| { |
| m_lag_sum = 0; |
| m_lag_count = 0; |
| m_lag_frames = 0; |
| |
| hr = WebmMfVp8Dec::SetDropMode(MF_DROP_MODE_NONE); |
| assert(SUCCEEDED(hr)); |
| |
| return S_OK; |
| } |
| |
| m_lag_sum += reftime; |
| ++m_lag_count; |
| |
| if (m_lag_count < 16) |
| return S_OK; |
| |
| const float lag_reftime = float(m_lag_sum) / float(m_lag_count); |
| |
| const float frames_ = lag_reftime * m_frame_rate / 10000000.0F; |
| const int frames = static_cast<int>(frames_); |
| |
| if (frames <= 1) // treat small values as noise |
| { |
| m_lag_sum = 0; |
| m_lag_count = 0; |
| m_lag_frames = 0; |
| |
| hr = WebmMfVp8Dec::SetDropMode(MF_DROP_MODE_NONE); |
| assert(SUCCEEDED(hr)); |
| } |
| |
| MF_QUALITY_DROP_MODE m; |
| |
| hr = WebmMfVp8Dec::GetDropMode(&m); |
| assert(SUCCEEDED(hr)); |
| |
| if ((m_lag_frames > 0) && // have previous value |
| (frames >= m_lag_frames) && // we're not getting better |
| (m < MF_DROP_MODE_5)) // TODO: use MF_QUALITY_CANNOT_KEEP_UP? |
| { |
| #ifdef _DEBUG |
| odbgstream os; |
| os << "WebmMfVp8Dec::NotifyQualityEvent: old drop mode=" << m; |
| #endif |
| |
| m = static_cast<MF_QUALITY_DROP_MODE>(int(m) + 1); |
| |
| #ifdef _DEBUG |
| os << " new drop mode=" << m << endl; |
| #endif |
| |
| hr = WebmMfVp8Dec::SetDropMode(m); |
| assert(SUCCEEDED(hr)); |
| } |
| |
| m_lag_sum = 0; |
| m_lag_count = 0; |
| m_lag_frames = frames; |
| |
| return S_OK; |
| |
| // Here's a case when there's been a long sequence of frames |
| // having positive lag. That means we're slightly behind, so |
| // we should compare how much we're behind now to how much |
| // we were behind in the recent past. The reason we do that |
| // is to discover whether we have a trend. |
| // |
| // How many data points to we use to compute the trend? We used |
| // the most recent 16 points to compute our latest lag value, so |
| // we must compare the most recent lag value to previous lag values. |
| // |
| // If we have a previous lag value, then we can compute a trend, even |
| // if we only have two points. |
| // |
| // If the avg lag computed here is same or (slightly?) larger, then it |
| // means we really are behind. We would choose a drop level. Our |
| // current algorithm even for dropping doesn't work that fast: at |
| // drop level 1, we drop only 1 frame for every 16 frames, so once |
| // we set a non-zero drop level we do have to wait at least 16 frames |
| // before reacting (which is what we do here). |
| // |
| // We don't necessarily have to compare lag times: we convert this to |
| // a frame, so we could compare frames instead. |
| // |
| // The issue remain: how often do we adjust the drop level? |
| // |
| // I suppose that when the reported lag time is less than 0, then this |
| // will reset everything anyway, so we can set the drop level back to |
| // 0 when that happens. |
| // |
| // We probalby only have to increase the drop level, until we're caught |
| // up, and it gets reset back to 0. The question is how often we adjust |
| // the drop level up. |
| // |
| // 1st time: 1 out of 16 |
| // 2nd time: 2 out of 16 |
| // 3rd time: 4 out of 16 |
| // 4th time: 8 out of 16 |
| // 5th time: keyframes only |
| // |
| // if lag has increased since last time, go ahead and increase the drop |
| // level. |
| // |
| // if lag has decreased since last time, the we can probably either decrease |
| // the drop level, or leave it as is. |
| |
| // These dampened out by themselves, so there's nothing special we needed |
| // to do. We don't want to react right away. The problem we need to |
| // handle |
| // is when the lag is positive and large, and stays constant or is |
| // increasing. |
| // It the number is trending down then the problem solves itself. The hard |
| // cases are when the lag is growing, or staying constant and large. |
| // |
| // How to define "growing"? If the slope is positive, that's growing. If |
| // we remain at any positive slope, the lag will definitely grow. |
| // |
| // We could use a one-shot flag: if the flag is true, throw away the next |
| // non-key frame, then clear the flag. |
| // |
| // Our goal is to pull the slope down to 0 (or negative). This should drive |
| // the lag down, which should drive the slope towards 0. |
| // |
| // A running average isn't necessarily what we want, although this |
| // will give us an estimate of the avg lag. If the avg is large and |
| // constant then we must correct. |
| |
| // How to compute avg lag: |
| // (sum of lag times) / (number of lag times) |
| // or could weigh curr avg and curr lag |
| // e.g. new avg lag = (15/16) * (old avg lag) + (1/16) * (new lag) |
| // if lag <= 0 then reset avg lag |
| |
| // 10000000 = 1 sec |
| // 5000000 = 1/2 sec |
| // 2500000 = 1/4 sec |
| // 1250000 = 1/8 sec |
| // 625000 = 1/16 sec |
| // 312500 = 1/32 sec |
| // 156250 = 1/64 sec |
| // 78125 = 1/128 sec |
| |
| // 1 sec late |
| // if we see 2 frames the set drop mode #5 (keyframe only) |
| // |
| // 1/2 sec late |
| // if we see 4 frames then set drop mode #4 |
| // |
| // 1/4 sec late |
| // if we see |
| |
| // drop mode 1 = 1/16 frames are dropped |
| // 2 = 1/8 frames |
| // 3 = 1/4 frames |
| // 4 = 1/2 frames |
| // |
| // If we do set the drop mode, then the decoder doesn't |
| // react that fast. |
| // |
| // for drop mode 1, we gain 1/16th sec every 16 frames |
| // if we were behind by 1/16th sec, then we'd catch up after 1 cycle |
| // if we were behind by 1/8th sec, we'd catch up after 2 cycles, etc |
| // |
| // for drop mode 2, we gain 1/8th sec every 8 frames |
| // if we were behind by 1/8th sec, we'd catch up after 1 cycle |
| // if we were behind by 1/4 sec, then we'd catch up after 2 cycles, etc |
| // |
| // for drop mode 3, we gain 1/4 sec every 4 frames |
| // if we were behind by 1/4 sec, we'd catch up after 1 cycle |
| // if we were behind by 1/2 sec, then we'd catch up after 2 cycles, etc |
| // |
| // for drop mode 4, we gain 1/2 sec every 2 frames |
| // |
| // THAT'S ALL WRONG |
| // the drop modes work in terms of frames, not times |
| // IQualityAdvise2 works in terms of time, not frames |
| // |
| // To convert between the two advise interfaces we need the frame rate |
| } |
| |
| void WebmMfVp8Dec::FrameRate::Init() { |
| m_numerator = 0; |
| m_denominator = 0; |
| } |
| |
| HRESULT WebmMfVp8Dec::FrameRate::AssignFrom(IMFMediaType* pmt) { |
| UINT32& num = m_numerator; |
| UINT32& den = m_denominator; |
| |
| const HRESULT hr = MFGetAttributeRatio(pmt, MF_MT_FRAME_RATE, &num, &den); |
| |
| // TODO: check explicitly for the result that means "attribute not set". |
| // MF_E_ATTRIBUTENOTFOUND |
| // MF_E_INVALIDTYPE |
| |
| if (FAILED(hr)) // we assume here this means value wasn't found |
| { |
| Init(); |
| return S_FALSE; |
| } |
| |
| if (den == 0) |
| return MF_E_INVALIDMEDIATYPE; |
| |
| if (num == 0) |
| return MF_E_INVALIDMEDIATYPE; |
| |
| return S_OK; |
| } |
| |
| HRESULT WebmMfVp8Dec::FrameRate::CopyTo(IMFMediaType* pmt) const { |
| const UINT32 num = m_numerator; |
| const UINT32 den = m_denominator; |
| |
| return MFSetAttributeRatio(pmt, MF_MT_FRAME_RATE, num, den); |
| } |
| |
| HRESULT WebmMfVp8Dec::FrameRate::CopyTo(float& value) const { |
| const UINT32 num = m_numerator; |
| const UINT32 den = m_denominator; |
| |
| if (den == 0) |
| value = -1; // serves as our "undefined framerate" nonce value |
| else |
| value = float(num) / float(den); |
| |
| return S_OK; |
| } |
| |
| bool WebmMfVp8Dec::FrameRate::Match(const FrameRate& rhs) const { |
| const UINT64 n_in = m_numerator; |
| const UINT64 d_in = m_denominator; |
| |
| const UINT64 n_out = rhs.m_numerator; |
| const UINT64 d_out = rhs.m_denominator; |
| |
| // n_in / d_in = n_out / d_out |
| // n_in * d_out = n_out * d_in |
| |
| return ((n_in * d_out) == (n_out * d_in)); |
| } |
| |
| } // end namespace WebmMfVp8DecLib |