blob: f735a34fffefb47917c7a8554e2706c45802ff7d [file]
#pragma warning(disable:4505) //unreferenced local function removed
#include "clockable.hpp"
#include <mfidl.h>
#include "vpx/vpx_decoder.h"
#include "vpx/vp8dx.h"
#include "webmmfvp8dec.hpp"
#include "webmtypes.hpp"
#include <mfapi.h>
#include <mferror.h>
#include <comdef.h>
#include <cassert>
#include <new>
#ifdef _DEBUG
#include "odbgstream.hpp"
#include "iidstr.hpp"
using std::endl;
using std::boolalpha;
#endif
_COM_SMARTPTR_TYPEDEF(IMFMediaBuffer, __uuidof(IMFMediaBuffer));
_COM_SMARTPTR_TYPEDEF(IMF2DBuffer, __uuidof(IMF2DBuffer));
namespace WebmMfVp8DecLib
{
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_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);
}
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.cpp
//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 > 0)
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, WebmTypes::MEDIASUBTYPE_VP80);
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)
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& vp8 = vpx_codec_vp8_dx_algo;
const int flags = 0; //TODO: VPX_CODEC_USE_POSTPROC;
const vpx_codec_err_t err = vpx_codec_dec_init(
&m_ctx,
&vp8,
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* const 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
//Y
const BYTE* pInY = f->planes[PLANE_Y];
assert(pInY);
unsigned int wIn = f->d_w;
unsigned int hIn = f->d_h;
BYTE* pOut = pOutBuf;
const int strideInY = f->stride[PLANE_Y];
for (unsigned int y = 0; y < hIn; ++y)
{
memcpy(pOut, pInY, wIn);
pInY += strideInY;
pOut += strideOut;
}
const BYTE* pInV = f->planes[PLANE_V];
assert(pInV);
const int strideInV = f->stride[PLANE_V];
const BYTE* pInU = f->planes[PLANE_U];
assert(pInU);
const int strideInU = f->stride[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