Fixed #1276: Add baseclasses sample in third_party directory required by dshow_dev



git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@4061 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/third_party/BaseClasses/ctlutil.cpp b/third_party/BaseClasses/ctlutil.cpp
new file mode 100644
index 0000000..8ccb9dc
--- /dev/null
+++ b/third_party/BaseClasses/ctlutil.cpp
@@ -0,0 +1,2541 @@
+//------------------------------------------------------------------------------

+// File: CtlUtil.cpp

+//

+// Desc: DirectShow base classes.

+//

+// Copyright (c) 1992-2001 Microsoft Corporation.  All rights reserved.

+//------------------------------------------------------------------------------

+

+

+// Base classes implementing IDispatch parsing for the basic control dual

+// interfaces. Derive from these and implement just the custom method and

+// property methods. We also implement CPosPassThru that can be used by

+// renderers and transforms to pass by IMediaPosition and IMediaSeeking

+

+

+#include <streams.h>

+#include <limits.h>

+#include "seekpt.h"

+

+// 'bool' non standard reserved word

+#pragma warning(disable:4237)

+

+

+// --- CBaseDispatch implementation ----------

+CBaseDispatch::~CBaseDispatch()

+{

+    if (m_pti) {

+	m_pti->Release();

+    }

+}

+

+

+// return 1 if we support GetTypeInfo

+

+STDMETHODIMP

+CBaseDispatch::GetTypeInfoCount(__out UINT * pctinfo)

+{

+    CheckPointer(pctinfo,E_POINTER);

+    ValidateReadWritePtr(pctinfo,sizeof(UINT *));

+    *pctinfo = 1;

+    return S_OK;

+}

+

+

+typedef HRESULT (STDAPICALLTYPE *LPLOADTYPELIB)(

+			    const OLECHAR FAR *szFile,

+			    __deref_out ITypeLib FAR* FAR* pptlib);

+

+typedef HRESULT (STDAPICALLTYPE *LPLOADREGTYPELIB)(REFGUID rguid,

+			    WORD wVerMajor,

+			    WORD wVerMinor,

+			    LCID lcid,

+			    __deref_out ITypeLib FAR* FAR* pptlib);

+

+// attempt to find our type library

+

+STDMETHODIMP

+CBaseDispatch::GetTypeInfo(

+  REFIID riid,

+  UINT itinfo,

+  LCID lcid,

+  __deref_out ITypeInfo ** pptinfo)

+{

+    CheckPointer(pptinfo,E_POINTER);

+    ValidateReadWritePtr(pptinfo,sizeof(ITypeInfo *));

+    HRESULT hr;

+

+    *pptinfo = NULL;

+

+    // we only support one type element

+    if (0 != itinfo) {

+	return TYPE_E_ELEMENTNOTFOUND;

+    }

+

+    if (NULL == pptinfo) {

+	return E_POINTER;

+    }

+

+    // always look for neutral

+    if (NULL == m_pti) {

+

+	LPLOADTYPELIB	    lpfnLoadTypeLib;

+	LPLOADREGTYPELIB    lpfnLoadRegTypeLib;

+	ITypeLib	    *ptlib;

+	HINSTANCE	    hInst;

+

+	static const char  szTypeLib[]	  = "LoadTypeLib";

+	static const char  szRegTypeLib[] = "LoadRegTypeLib";

+	static const WCHAR szControl[]	  = L"control.tlb";

+

+	//

+	// Try to get the Ole32Aut.dll module handle.

+	//

+

+	hInst = LoadOLEAut32();

+	if (hInst == NULL) {

+	    DWORD dwError = GetLastError();

+	    return AmHresultFromWin32(dwError);

+	}

+	lpfnLoadRegTypeLib = (LPLOADREGTYPELIB)GetProcAddress(hInst,

+							      szRegTypeLib);

+	if (lpfnLoadRegTypeLib == NULL) {

+	    DWORD dwError = GetLastError();

+	    return AmHresultFromWin32(dwError);

+	}

+

+	hr = (*lpfnLoadRegTypeLib)(LIBID_QuartzTypeLib, 1, 0, // version 1.0

+				   lcid, &ptlib);

+

+	if (FAILED(hr)) {

+

+	    // attempt to load directly - this will fill the

+	    // registry in if it finds it

+

+	    lpfnLoadTypeLib = (LPLOADTYPELIB)GetProcAddress(hInst, szTypeLib);

+	    if (lpfnLoadTypeLib == NULL) {

+		DWORD dwError = GetLastError();

+		return AmHresultFromWin32(dwError);

+	    }

+

+	    hr = (*lpfnLoadTypeLib)(szControl, &ptlib);

+	    if (FAILED(hr)) {

+		return hr;

+	    }

+	}

+

+	hr = ptlib->GetTypeInfoOfGuid(

+		    riid,

+		    &m_pti);

+

+	ptlib->Release();

+

+	if (FAILED(hr)) {

+	    return hr;

+	}

+    }

+

+    *pptinfo = m_pti;

+    m_pti->AddRef();

+    return S_OK;

+}

+

+

+STDMETHODIMP

+CBaseDispatch::GetIDsOfNames(

+  REFIID riid,

+  __in_ecount(cNames) LPOLESTR * rgszNames,

+  UINT cNames,

+  LCID lcid,

+  __out_ecount(cNames) DISPID * rgdispid)

+{

+    // although the IDispatch riid is dead, we use this to pass from

+    // the interface implementation class to us the iid we are talking about.

+

+    ITypeInfo * pti;

+    HRESULT hr = GetTypeInfo(riid, 0, lcid, &pti);

+

+    if (SUCCEEDED(hr)) {

+	hr = pti->GetIDsOfNames(rgszNames, cNames, rgdispid);

+

+	pti->Release();

+    }

+    return hr;

+}

+

+

+// --- CMediaControl implementation ---------

+

+CMediaControl::CMediaControl(const TCHAR * name,LPUNKNOWN pUnk) :

+    CUnknown(name, pUnk)

+{

+}

+

+// expose our interfaces IMediaControl and IUnknown

+

+STDMETHODIMP

+CMediaControl::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv)

+{

+    ValidateReadWritePtr(ppv,sizeof(PVOID));

+    if (riid == IID_IMediaControl) {

+	return GetInterface( (IMediaControl *) this, ppv);

+    } else {

+	return CUnknown::NonDelegatingQueryInterface(riid, ppv);

+    }

+}

+

+

+// return 1 if we support GetTypeInfo

+

+STDMETHODIMP

+CMediaControl::GetTypeInfoCount(__out UINT * pctinfo)

+{

+    return m_basedisp.GetTypeInfoCount(pctinfo);

+}

+

+

+// attempt to find our type library

+

+STDMETHODIMP

+CMediaControl::GetTypeInfo(

+  UINT itinfo,

+  LCID lcid,

+  __deref_out ITypeInfo ** pptinfo)

+{

+    return m_basedisp.GetTypeInfo(

+		IID_IMediaControl,

+		itinfo,

+		lcid,

+		pptinfo);

+}

+

+

+STDMETHODIMP

+CMediaControl::GetIDsOfNames(

+  REFIID riid,

+  __in_ecount(cNames) LPOLESTR * rgszNames,

+  UINT cNames,

+  LCID lcid,

+  __out_ecount(cNames) DISPID * rgdispid)

+{

+    return m_basedisp.GetIDsOfNames(

+			IID_IMediaControl,

+			rgszNames,

+			cNames,

+			lcid,

+			rgdispid);

+}

+

+

+STDMETHODIMP

+CMediaControl::Invoke(

+  DISPID dispidMember,

+  REFIID riid,

+  LCID lcid,

+  WORD wFlags,

+  __in DISPPARAMS * pdispparams,

+  __out_opt VARIANT * pvarResult,

+  __out_opt EXCEPINFO * pexcepinfo,

+  __out_opt UINT * puArgErr)

+{

+    // this parameter is a dead leftover from an earlier interface

+    if (IID_NULL != riid) {

+	return DISP_E_UNKNOWNINTERFACE;

+    }

+

+    ITypeInfo * pti;

+    HRESULT hr = GetTypeInfo(0, lcid, &pti);

+

+    if (FAILED(hr)) {

+	return hr;

+    }

+

+    hr = pti->Invoke(

+	    (IMediaControl *)this,

+	    dispidMember,

+	    wFlags,

+	    pdispparams,

+	    pvarResult,

+	    pexcepinfo,

+	    puArgErr);

+

+    pti->Release();

+    return hr;

+}

+

+

+// --- CMediaEvent implementation ----------

+

+

+CMediaEvent::CMediaEvent(__in_opt LPCTSTR name,__in_opt LPUNKNOWN pUnk) :

+    CUnknown(name, pUnk)

+{

+}

+

+

+// expose our interfaces IMediaEvent and IUnknown

+

+STDMETHODIMP

+CMediaEvent::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv)

+{

+    ValidateReadWritePtr(ppv,sizeof(PVOID));

+    if (riid == IID_IMediaEvent || riid == IID_IMediaEventEx) {

+	return GetInterface( (IMediaEventEx *) this, ppv);

+    } else {

+	return CUnknown::NonDelegatingQueryInterface(riid, ppv);

+    }

+}

+

+

+// return 1 if we support GetTypeInfo

+

+STDMETHODIMP

+CMediaEvent::GetTypeInfoCount(__out UINT * pctinfo)

+{

+    return m_basedisp.GetTypeInfoCount(pctinfo);

+}

+

+

+// attempt to find our type library

+

+STDMETHODIMP

+CMediaEvent::GetTypeInfo(

+  UINT itinfo,

+  LCID lcid,

+  __deref_out ITypeInfo ** pptinfo)

+{

+    return m_basedisp.GetTypeInfo(

+		IID_IMediaEvent,

+		itinfo,

+		lcid,

+		pptinfo);

+}

+

+

+STDMETHODIMP

+CMediaEvent::GetIDsOfNames(

+  REFIID riid,

+  __in_ecount(cNames) LPOLESTR * rgszNames,

+  UINT cNames,

+  LCID lcid,

+  __out_ecount(cNames) DISPID * rgdispid)

+{

+    return m_basedisp.GetIDsOfNames(

+			IID_IMediaEvent,

+			rgszNames,

+			cNames,

+			lcid,

+			rgdispid);

+}

+

+

+STDMETHODIMP

+CMediaEvent::Invoke(

+  DISPID dispidMember,

+  REFIID riid,

+  LCID lcid,

+  WORD wFlags,

+  __in DISPPARAMS * pdispparams,

+  __out_opt VARIANT * pvarResult,

+  __out_opt EXCEPINFO * pexcepinfo,

+  __out_opt UINT * puArgErr)

+{

+    // this parameter is a dead leftover from an earlier interface

+    if (IID_NULL != riid) {

+	return DISP_E_UNKNOWNINTERFACE;

+    }

+

+    ITypeInfo * pti;

+    HRESULT hr = GetTypeInfo(0, lcid, &pti);

+

+    if (FAILED(hr)) {

+	return hr;

+    }

+

+    hr = pti->Invoke(

+	    (IMediaEvent *)this,

+	    dispidMember,

+	    wFlags,

+	    pdispparams,

+	    pvarResult,

+	    pexcepinfo,

+	    puArgErr);

+

+    pti->Release();

+    return hr;

+}

+

+

+// --- CMediaPosition implementation ----------

+

+

+CMediaPosition::CMediaPosition(__in_opt LPCTSTR name,__in_opt LPUNKNOWN pUnk) :

+    CUnknown(name, pUnk)

+{

+}

+

+CMediaPosition::CMediaPosition(__in_opt LPCTSTR name,

+                               __in_opt LPUNKNOWN pUnk,

+                               __inout HRESULT * phr) :

+    CUnknown(name, pUnk)

+{

+    UNREFERENCED_PARAMETER(phr);

+}

+

+

+// expose our interfaces IMediaPosition and IUnknown

+

+STDMETHODIMP

+CMediaPosition::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv)

+{

+    ValidateReadWritePtr(ppv,sizeof(PVOID));

+    if (riid == IID_IMediaPosition) {

+	return GetInterface( (IMediaPosition *) this, ppv);

+    } else {

+	return CUnknown::NonDelegatingQueryInterface(riid, ppv);

+    }

+}

+

+

+// return 1 if we support GetTypeInfo

+

+STDMETHODIMP

+CMediaPosition::GetTypeInfoCount(__out UINT * pctinfo)

+{

+    return m_basedisp.GetTypeInfoCount(pctinfo);

+}

+

+

+// attempt to find our type library

+

+STDMETHODIMP

+CMediaPosition::GetTypeInfo(

+  UINT itinfo,

+  LCID lcid,

+  __deref_out ITypeInfo ** pptinfo)

+{

+    return m_basedisp.GetTypeInfo(

+		IID_IMediaPosition,

+		itinfo,

+		lcid,

+		pptinfo);

+}

+

+

+STDMETHODIMP

+CMediaPosition::GetIDsOfNames(

+  REFIID riid,

+  __in_ecount(cNames) LPOLESTR * rgszNames,

+  UINT cNames,

+  LCID lcid,

+  __out_ecount(cNames) DISPID * rgdispid)

+{

+    return m_basedisp.GetIDsOfNames(

+			IID_IMediaPosition,

+			rgszNames,

+			cNames,

+			lcid,

+			rgdispid);

+}

+

+

+STDMETHODIMP

+CMediaPosition::Invoke(

+  DISPID dispidMember,

+  REFIID riid,

+  LCID lcid,

+  WORD wFlags,

+  __in DISPPARAMS * pdispparams,

+  __out_opt VARIANT * pvarResult,

+  __out_opt EXCEPINFO * pexcepinfo,

+  __out_opt UINT * puArgErr)

+{

+    // this parameter is a dead leftover from an earlier interface

+    if (IID_NULL != riid) {

+	return DISP_E_UNKNOWNINTERFACE;

+    }

+

+    ITypeInfo * pti;

+    HRESULT hr = GetTypeInfo(0, lcid, &pti);

+

+    if (FAILED(hr)) {

+	return hr;

+    }

+

+    hr = pti->Invoke(

+	    (IMediaPosition *)this,

+	    dispidMember,

+	    wFlags,

+	    pdispparams,

+	    pvarResult,

+	    pexcepinfo,

+	    puArgErr);

+

+    pti->Release();

+    return hr;

+}

+

+

+// --- IMediaPosition and IMediaSeeking pass through class ----------

+

+

+CPosPassThru::CPosPassThru(__in_opt LPCTSTR pName,

+			   __in_opt LPUNKNOWN pUnk,

+			   __inout HRESULT *phr,

+			   IPin *pPin) :

+    CMediaPosition(pName,pUnk),

+    m_pPin(pPin)

+{

+    if (pPin == NULL) {

+	*phr = E_POINTER;

+	return;

+    }

+}

+

+

+// Expose our IMediaSeeking and IMediaPosition interfaces

+

+STDMETHODIMP

+CPosPassThru::NonDelegatingQueryInterface(REFIID riid,__deref_out void **ppv)

+{

+    CheckPointer(ppv,E_POINTER);

+    *ppv = NULL;

+

+    if (riid == IID_IMediaSeeking) {

+	return GetInterface( static_cast<IMediaSeeking *>(this), ppv);

+    }

+    return CMediaPosition::NonDelegatingQueryInterface(riid,ppv);

+}

+

+

+// Return the IMediaPosition interface from our peer

+

+HRESULT

+CPosPassThru::GetPeer(IMediaPosition ** ppMP)

+{

+    *ppMP = NULL;

+

+    IPin *pConnected;

+    HRESULT hr = m_pPin->ConnectedTo(&pConnected);

+    if (FAILED(hr)) {

+	return E_NOTIMPL;

+    }

+    IMediaPosition * pMP;

+    hr = pConnected->QueryInterface(IID_IMediaPosition, (void **) &pMP);

+    pConnected->Release();

+    if (FAILED(hr)) {

+	return E_NOTIMPL;

+    }

+

+    *ppMP = pMP;

+    return S_OK;

+}

+

+

+// Return the IMediaSeeking interface from our peer

+

+HRESULT

+CPosPassThru::GetPeerSeeking(__deref_out IMediaSeeking ** ppMS)

+{

+    *ppMS = NULL;

+

+    IPin *pConnected;

+    HRESULT hr = m_pPin->ConnectedTo(&pConnected);

+    if (FAILED(hr)) {

+	return E_NOTIMPL;

+    }

+    IMediaSeeking * pMS;

+    hr = pConnected->QueryInterface(IID_IMediaSeeking, (void **) &pMS);

+    pConnected->Release();

+    if (FAILED(hr)) {

+	return E_NOTIMPL;

+    }

+

+    *ppMS = pMS;

+    return S_OK;

+}

+

+

+// --- IMediaSeeking methods ----------

+

+

+STDMETHODIMP

+CPosPassThru::GetCapabilities(__out DWORD * pCaps)

+{

+    IMediaSeeking* pMS;

+    HRESULT hr = GetPeerSeeking(&pMS);

+    if (FAILED(hr)) {

+	return hr;

+    }

+

+    hr = pMS->GetCapabilities(pCaps);

+    pMS->Release();

+    return hr;

+}

+

+STDMETHODIMP

+CPosPassThru::CheckCapabilities(__inout DWORD * pCaps)

+{

+    IMediaSeeking* pMS;

+    HRESULT hr = GetPeerSeeking(&pMS);

+    if (FAILED(hr)) {

+	return hr;

+    }

+

+    hr = pMS->CheckCapabilities(pCaps);

+    pMS->Release();

+    return hr;

+}

+

+STDMETHODIMP

+CPosPassThru::IsFormatSupported(const GUID * pFormat)

+{

+    IMediaSeeking* pMS;

+    HRESULT hr = GetPeerSeeking(&pMS);

+    if (FAILED(hr)) {

+	return hr;

+    }

+

+    hr = pMS->IsFormatSupported(pFormat);

+    pMS->Release();

+    return hr;

+}

+

+

+STDMETHODIMP

+CPosPassThru::QueryPreferredFormat(__out GUID *pFormat)

+{

+    IMediaSeeking* pMS;

+    HRESULT hr = GetPeerSeeking(&pMS);

+    if (FAILED(hr)) {

+	return hr;

+    }

+

+    hr = pMS->QueryPreferredFormat(pFormat);

+    pMS->Release();

+    return hr;

+}

+

+

+STDMETHODIMP

+CPosPassThru::SetTimeFormat(const GUID * pFormat)

+{

+    IMediaSeeking* pMS;

+    HRESULT hr = GetPeerSeeking(&pMS);

+    if (FAILED(hr)) {

+	return hr;

+    }

+

+    hr = pMS->SetTimeFormat(pFormat);

+    pMS->Release();

+    return hr;

+}

+

+

+STDMETHODIMP

+CPosPassThru::GetTimeFormat(__out GUID *pFormat)

+{

+    IMediaSeeking* pMS;

+    HRESULT hr = GetPeerSeeking(&pMS);

+    if (FAILED(hr)) {

+	return hr;

+    }

+

+    hr = pMS->GetTimeFormat(pFormat);

+    pMS->Release();

+    return hr;

+}

+

+

+STDMETHODIMP

+CPosPassThru::IsUsingTimeFormat(const GUID * pFormat)

+{

+    IMediaSeeking* pMS;

+    HRESULT hr = GetPeerSeeking(&pMS);

+    if (FAILED(hr)) {

+	return hr;

+    }

+

+    hr = pMS->IsUsingTimeFormat(pFormat);

+    pMS->Release();

+    return hr;

+}

+

+

+STDMETHODIMP

+CPosPassThru::ConvertTimeFormat(__out LONGLONG * pTarget, 

+                                __in_opt const GUID * pTargetFormat,

+				LONGLONG Source, 

+                                __in_opt const GUID * pSourceFormat )

+{

+    IMediaSeeking* pMS;

+    HRESULT hr = GetPeerSeeking(&pMS);

+    if (FAILED(hr)) {

+	return hr;

+    }

+

+    hr = pMS->ConvertTimeFormat(pTarget, pTargetFormat, Source, pSourceFormat );

+    pMS->Release();

+    return hr;

+}

+

+

+STDMETHODIMP

+CPosPassThru::SetPositions( __inout_opt LONGLONG * pCurrent, 

+                            DWORD CurrentFlags, 

+                            __inout_opt LONGLONG * pStop, 

+                            DWORD StopFlags )

+{

+    IMediaSeeking* pMS;

+    HRESULT hr = GetPeerSeeking(&pMS);

+    if (FAILED(hr)) {

+	return hr;

+    }

+

+    hr = pMS->SetPositions(pCurrent, CurrentFlags, pStop, StopFlags );

+    pMS->Release();

+    return hr;

+}

+

+STDMETHODIMP

+CPosPassThru::GetPositions(__out_opt LONGLONG *pCurrent, __out_opt LONGLONG * pStop)

+{

+    IMediaSeeking* pMS;

+    HRESULT hr = GetPeerSeeking(&pMS);

+    if (FAILED(hr)) {

+	return hr;

+    }

+

+    hr = pMS->GetPositions(pCurrent,pStop);

+    pMS->Release();

+    return hr;

+}

+

+HRESULT

+CPosPassThru::GetSeekingLongLong

+( HRESULT (__stdcall IMediaSeeking::*pMethod)( __out LONGLONG * )

+, LONGLONG * pll

+)

+{

+    IMediaSeeking* pMS;

+    HRESULT hr = GetPeerSeeking(&pMS);

+    if (SUCCEEDED(hr))

+    {

+	hr = (pMS->*pMethod)(pll);

+	pMS->Release();

+    }

+    return hr;

+}

+

+// If we don't have a current position then ask upstream

+

+STDMETHODIMP

+CPosPassThru::GetCurrentPosition(__out LONGLONG *pCurrent)

+{

+    // Can we report the current position

+    HRESULT hr = GetMediaTime(pCurrent,NULL);

+    if (SUCCEEDED(hr)) hr = NOERROR;

+    else hr = GetSeekingLongLong( &IMediaSeeking::GetCurrentPosition, pCurrent );

+    return hr;

+}

+

+

+STDMETHODIMP

+CPosPassThru::GetStopPosition(__out LONGLONG *pStop)

+{

+    return GetSeekingLongLong( &IMediaSeeking::GetStopPosition, pStop );;

+}

+

+STDMETHODIMP

+CPosPassThru::GetDuration(__out LONGLONG *pDuration)

+{

+    return GetSeekingLongLong( &IMediaSeeking::GetDuration, pDuration );;

+}

+

+

+STDMETHODIMP

+CPosPassThru::GetPreroll(__out LONGLONG *pllPreroll)

+{

+    return GetSeekingLongLong( &IMediaSeeking::GetPreroll, pllPreroll );;

+}

+

+

+STDMETHODIMP

+CPosPassThru::GetAvailable( __out_opt LONGLONG *pEarliest, __out_opt LONGLONG *pLatest )

+{

+    IMediaSeeking* pMS;

+    HRESULT hr = GetPeerSeeking(&pMS);

+    if (FAILED(hr)) {

+	return hr;

+    }

+

+    hr = pMS->GetAvailable( pEarliest, pLatest );

+    pMS->Release();

+    return hr;

+}

+

+

+STDMETHODIMP

+CPosPassThru::GetRate(__out double * pdRate)

+{

+    IMediaSeeking* pMS;

+    HRESULT hr = GetPeerSeeking(&pMS);

+    if (FAILED(hr)) {

+	return hr;

+    }

+    hr = pMS->GetRate(pdRate);

+    pMS->Release();

+    return hr;

+}

+

+

+STDMETHODIMP

+CPosPassThru::SetRate(double dRate)

+{

+    if (0.0 == dRate) {

+		return E_INVALIDARG;

+    }

+

+    IMediaSeeking* pMS;

+    HRESULT hr = GetPeerSeeking(&pMS);

+    if (FAILED(hr)) {

+	return hr;

+    }

+    hr = pMS->SetRate(dRate);

+    pMS->Release();

+    return hr;

+}

+

+

+

+

+// --- IMediaPosition methods ----------

+

+

+STDMETHODIMP

+CPosPassThru::get_Duration(__out REFTIME * plength)

+{

+    IMediaPosition* pMP;

+    HRESULT hr = GetPeer(&pMP);

+    if (FAILED(hr)) {

+	return hr;

+    }

+

+    hr = pMP->get_Duration(plength);

+    pMP->Release();

+    return hr;

+}

+

+

+STDMETHODIMP

+CPosPassThru::get_CurrentPosition(__out REFTIME * pllTime)

+{

+    IMediaPosition* pMP;

+    HRESULT hr = GetPeer(&pMP);

+    if (FAILED(hr)) {

+	return hr;

+    }

+    hr = pMP->get_CurrentPosition(pllTime);

+    pMP->Release();

+    return hr;

+}

+

+

+STDMETHODIMP

+CPosPassThru::put_CurrentPosition(REFTIME llTime)

+{

+    IMediaPosition* pMP;

+    HRESULT hr = GetPeer(&pMP);

+    if (FAILED(hr)) {

+	return hr;

+    }

+    hr = pMP->put_CurrentPosition(llTime);

+    pMP->Release();

+    return hr;

+}

+

+

+STDMETHODIMP

+CPosPassThru::get_StopTime(__out REFTIME * pllTime)

+{

+    IMediaPosition* pMP;

+    HRESULT hr = GetPeer(&pMP);

+    if (FAILED(hr)) {

+	return hr;

+    }

+    hr = pMP->get_StopTime(pllTime);

+    pMP->Release();

+    return hr;

+}

+

+

+STDMETHODIMP

+CPosPassThru::put_StopTime(REFTIME llTime)

+{

+    IMediaPosition* pMP;

+    HRESULT hr = GetPeer(&pMP);

+    if (FAILED(hr)) {

+	return hr;

+    }

+    hr = pMP->put_StopTime(llTime);

+    pMP->Release();

+    return hr;

+}

+

+

+STDMETHODIMP

+CPosPassThru::get_PrerollTime(__out REFTIME * pllTime)

+{

+    IMediaPosition* pMP;

+    HRESULT hr = GetPeer(&pMP);

+    if (FAILED(hr)) {

+	return hr;

+    }

+    hr = pMP->get_PrerollTime(pllTime);

+    pMP->Release();

+    return hr;

+}

+

+

+STDMETHODIMP

+CPosPassThru::put_PrerollTime(REFTIME llTime)

+{

+    IMediaPosition* pMP;

+    HRESULT hr = GetPeer(&pMP);

+    if (FAILED(hr)) {

+	return hr;

+    }

+    hr = pMP->put_PrerollTime(llTime);

+    pMP->Release();

+    return hr;

+}

+

+

+STDMETHODIMP

+CPosPassThru::get_Rate(__out double * pdRate)

+{

+    IMediaPosition* pMP;

+    HRESULT hr = GetPeer(&pMP);

+    if (FAILED(hr)) {

+	return hr;

+    }

+    hr = pMP->get_Rate(pdRate);

+    pMP->Release();

+    return hr;

+}

+

+

+STDMETHODIMP

+CPosPassThru::put_Rate(double dRate)

+{

+    if (0.0 == dRate) {

+		return E_INVALIDARG;

+    }

+

+    IMediaPosition* pMP;

+    HRESULT hr = GetPeer(&pMP);

+    if (FAILED(hr)) {

+	return hr;

+    }

+    hr = pMP->put_Rate(dRate);

+    pMP->Release();

+    return hr;

+}

+

+

+STDMETHODIMP

+CPosPassThru::CanSeekForward(__out LONG *pCanSeekForward)

+{

+    IMediaPosition* pMP;

+    HRESULT hr = GetPeer(&pMP);

+    if (FAILED(hr)) {

+	return hr;

+    }

+    hr = pMP->CanSeekForward(pCanSeekForward);

+    pMP->Release();

+    return hr;

+}

+

+

+STDMETHODIMP

+CPosPassThru::CanSeekBackward(__out LONG *pCanSeekBackward)

+{

+    IMediaPosition* pMP;

+    HRESULT hr = GetPeer(&pMP);

+    if (FAILED(hr)) {

+	return hr;

+    }

+    hr = pMP->CanSeekBackward(pCanSeekBackward);

+    pMP->Release();

+    return hr;

+}

+

+

+// --- Implements the CRendererPosPassThru class ----------

+

+

+// Media times (eg current frame, field, sample etc) are passed through the

+// filtergraph in media samples. When a renderer gets a sample with media

+// times in it, it will call one of the RegisterMediaTime methods we expose

+// (one takes an IMediaSample, the other takes the media times direct). We

+// store the media times internally and return them in GetCurrentPosition.

+

+CRendererPosPassThru::CRendererPosPassThru(__in_opt LPCTSTR pName,

+					   __in_opt LPUNKNOWN pUnk,

+					   __inout HRESULT *phr,

+					   IPin *pPin) :

+    CPosPassThru(pName,pUnk,phr,pPin),

+    m_StartMedia(0),

+    m_EndMedia(0),

+    m_bReset(TRUE)

+{

+}

+

+

+// Sets the media times the object should report

+

+HRESULT

+CRendererPosPassThru::RegisterMediaTime(IMediaSample *pMediaSample)

+{

+    ASSERT(pMediaSample);

+    LONGLONG StartMedia;

+    LONGLONG EndMedia;

+

+    CAutoLock cAutoLock(&m_PositionLock);

+

+    // Get the media times from the sample

+

+    HRESULT hr = pMediaSample->GetTime(&StartMedia,&EndMedia);

+    if (FAILED(hr))

+    {

+	ASSERT(hr == VFW_E_SAMPLE_TIME_NOT_SET);

+	return hr;

+    }

+

+    m_StartMedia = StartMedia;

+    m_EndMedia = EndMedia;

+    m_bReset = FALSE;

+    return NOERROR;

+}

+

+

+// Sets the media times the object should report

+

+HRESULT

+CRendererPosPassThru::RegisterMediaTime(LONGLONG StartTime,LONGLONG EndTime)

+{

+    CAutoLock cAutoLock(&m_PositionLock);

+    m_StartMedia = StartTime;

+    m_EndMedia = EndTime;

+    m_bReset = FALSE;

+    return NOERROR;

+}

+

+

+// Return the current media times registered in the object

+

+HRESULT

+CRendererPosPassThru::GetMediaTime(__out LONGLONG *pStartTime, __out_opt LONGLONG *pEndTime)

+{

+    ASSERT(pStartTime);

+

+    CAutoLock cAutoLock(&m_PositionLock);

+    if (m_bReset == TRUE) {

+	return E_FAIL;

+    }

+

+    // We don't have to return the end time

+

+    HRESULT hr = ConvertTimeFormat( pStartTime, 0, m_StartMedia, &TIME_FORMAT_MEDIA_TIME );

+    if (pEndTime && SUCCEEDED(hr)) {

+	hr = ConvertTimeFormat( pEndTime, 0, m_EndMedia, &TIME_FORMAT_MEDIA_TIME );

+    }

+    return hr;

+}

+

+

+// Resets the media times we hold

+

+HRESULT

+CRendererPosPassThru::ResetMediaTime()

+{

+    CAutoLock cAutoLock(&m_PositionLock);

+    m_StartMedia = 0;

+    m_EndMedia = 0;

+    m_bReset = TRUE;

+    return NOERROR;

+}

+

+// Intended to be called by the owing filter during EOS processing so

+// that the media times can be adjusted to the stop time.  This ensures

+// that the GetCurrentPosition will actully get to the stop position.

+HRESULT

+CRendererPosPassThru::EOS()

+{

+    HRESULT hr;

+

+    if ( m_bReset == TRUE ) hr = E_FAIL;

+    else

+    {

+	LONGLONG llStop;

+	if SUCCEEDED(hr=GetStopPosition(&llStop))

+	{

+	    CAutoLock cAutoLock(&m_PositionLock);

+	    m_StartMedia =

+	    m_EndMedia	 = llStop;

+	}

+    }

+    return hr;

+}

+

+// -- CSourceSeeking implementation ------------

+

+CSourceSeeking::CSourceSeeking(

+    __in_opt LPCTSTR pName,

+    __in_opt LPUNKNOWN pUnk,

+    __inout HRESULT* phr,

+    __in CCritSec * pLock) :

+        CUnknown(pName, pUnk),

+        m_pLock(pLock),

+        m_rtStart((long)0)

+{

+    m_rtStop = _I64_MAX / 2;

+    m_rtDuration = m_rtStop;

+    m_dRateSeeking = 1.0;

+

+    m_dwSeekingCaps = AM_SEEKING_CanSeekForwards

+        | AM_SEEKING_CanSeekBackwards

+        | AM_SEEKING_CanSeekAbsolute

+        | AM_SEEKING_CanGetStopPos

+        | AM_SEEKING_CanGetDuration;

+}

+

+HRESULT CSourceSeeking::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv)

+{

+    if(riid == IID_IMediaSeeking) {

+        CheckPointer(ppv, E_POINTER);

+        return GetInterface(static_cast<IMediaSeeking *>(this), ppv);

+    }

+    else {

+        return CUnknown::NonDelegatingQueryInterface(riid, ppv);

+    }

+}

+

+

+HRESULT CSourceSeeking::IsFormatSupported(const GUID * pFormat)

+{

+    CheckPointer(pFormat, E_POINTER);

+    // only seeking in time (REFERENCE_TIME units) is supported

+    return *pFormat == TIME_FORMAT_MEDIA_TIME ? S_OK : S_FALSE;

+}

+

+HRESULT CSourceSeeking::QueryPreferredFormat(__out GUID *pFormat)

+{

+    CheckPointer(pFormat, E_POINTER);

+    *pFormat = TIME_FORMAT_MEDIA_TIME;

+    return S_OK;

+}

+

+HRESULT CSourceSeeking::SetTimeFormat(const GUID * pFormat)

+{

+    CheckPointer(pFormat, E_POINTER);

+

+    // nothing to set; just check that it's TIME_FORMAT_TIME

+    return *pFormat == TIME_FORMAT_MEDIA_TIME ? S_OK : E_INVALIDARG;

+}

+

+HRESULT CSourceSeeking::IsUsingTimeFormat(const GUID * pFormat)

+{

+    CheckPointer(pFormat, E_POINTER);

+    return *pFormat == TIME_FORMAT_MEDIA_TIME ? S_OK : S_FALSE;

+}

+

+HRESULT CSourceSeeking::GetTimeFormat(__out GUID *pFormat)

+{

+    CheckPointer(pFormat, E_POINTER);

+    *pFormat = TIME_FORMAT_MEDIA_TIME;

+    return S_OK;

+}

+

+HRESULT CSourceSeeking::GetDuration(__out LONGLONG *pDuration)

+{

+    CheckPointer(pDuration, E_POINTER);

+    CAutoLock lock(m_pLock);

+    *pDuration = m_rtDuration;

+    return S_OK;

+}

+

+HRESULT CSourceSeeking::GetStopPosition(__out LONGLONG *pStop)

+{

+    CheckPointer(pStop, E_POINTER);

+    CAutoLock lock(m_pLock);

+    *pStop = m_rtStop;

+    return S_OK;

+}

+

+HRESULT CSourceSeeking::GetCurrentPosition(__out LONGLONG *pCurrent)

+{

+    // GetCurrentPosition is typically supported only in renderers and

+    // not in source filters.

+    return E_NOTIMPL;

+}

+

+HRESULT CSourceSeeking::GetCapabilities( __out DWORD * pCapabilities )

+{

+    CheckPointer(pCapabilities, E_POINTER);

+    *pCapabilities = m_dwSeekingCaps;

+    return S_OK;

+}

+

+HRESULT CSourceSeeking::CheckCapabilities( __inout DWORD * pCapabilities )

+{

+    CheckPointer(pCapabilities, E_POINTER);

+

+    // make sure all requested capabilities are in our mask

+    return (~m_dwSeekingCaps & *pCapabilities) ? S_FALSE : S_OK;

+}

+

+HRESULT CSourceSeeking::ConvertTimeFormat( __out LONGLONG * pTarget, 

+                                           __in_opt const GUID * pTargetFormat,

+                                           LONGLONG Source, 

+                                           __in_opt const GUID * pSourceFormat )

+{

+    CheckPointer(pTarget, E_POINTER);

+    // format guids can be null to indicate current format

+

+    // since we only support TIME_FORMAT_MEDIA_TIME, we don't really

+    // offer any conversions.

+    if(pTargetFormat == 0 || *pTargetFormat == TIME_FORMAT_MEDIA_TIME)

+    {

+        if(pSourceFormat == 0 || *pSourceFormat == TIME_FORMAT_MEDIA_TIME)

+        {

+            *pTarget = Source;

+            return S_OK;

+        }

+    }

+

+    return E_INVALIDARG;

+}

+

+

+HRESULT CSourceSeeking::SetPositions( __inout_opt LONGLONG * pCurrent,  

+                                      DWORD CurrentFlags, 

+                                      __inout_opt LONGLONG * pStop,  

+                                      DWORD StopFlags )

+{

+    DWORD StopPosBits = StopFlags & AM_SEEKING_PositioningBitsMask;

+    DWORD StartPosBits = CurrentFlags & AM_SEEKING_PositioningBitsMask;

+

+    if(StopFlags) {

+        CheckPointer(pStop, E_POINTER);

+

+        // accept only relative, incremental, or absolute positioning

+        if(StopPosBits != StopFlags) {

+            return E_INVALIDARG;

+        }

+    }

+

+    if(CurrentFlags) {

+        CheckPointer(pCurrent, E_POINTER);

+        if(StartPosBits != AM_SEEKING_AbsolutePositioning &&

+           StartPosBits != AM_SEEKING_RelativePositioning) {

+            return E_INVALIDARG;

+        }

+    }

+

+

+    // scope for autolock

+    {

+        CAutoLock lock(m_pLock);

+

+        // set start position

+        if(StartPosBits == AM_SEEKING_AbsolutePositioning)

+        {

+            m_rtStart = *pCurrent;

+        }

+        else if(StartPosBits == AM_SEEKING_RelativePositioning)

+        {

+            m_rtStart += *pCurrent;

+        }

+

+        // set stop position

+        if(StopPosBits == AM_SEEKING_AbsolutePositioning)

+        {

+            m_rtStop = *pStop;

+        }

+        else if(StopPosBits == AM_SEEKING_IncrementalPositioning)

+        {

+            m_rtStop = m_rtStart + *pStop;

+        }

+        else if(StopPosBits == AM_SEEKING_RelativePositioning)

+        {

+            m_rtStop = m_rtStop + *pStop;

+        }

+    }

+

+

+    HRESULT hr = S_OK;

+    if(SUCCEEDED(hr) && StopPosBits) {

+        hr = ChangeStop();

+    }

+    if(StartPosBits) {

+        hr = ChangeStart();

+    }

+

+    return hr;

+}

+

+

+HRESULT CSourceSeeking::GetPositions( __out_opt LONGLONG * pCurrent, __out_opt LONGLONG * pStop )

+{

+    if(pCurrent) {

+        *pCurrent = m_rtStart;

+    }

+    if(pStop) {

+        *pStop = m_rtStop;

+    }

+

+    return S_OK;;

+}

+

+

+HRESULT CSourceSeeking::GetAvailable( __out_opt LONGLONG * pEarliest, __out_opt LONGLONG * pLatest )

+{

+    if(pEarliest) {

+        *pEarliest = 0;

+    }

+    if(pLatest) {

+        CAutoLock lock(m_pLock);

+        *pLatest = m_rtDuration;

+    }

+    return S_OK;

+}

+

+HRESULT CSourceSeeking::SetRate( double dRate)

+{

+    {

+        CAutoLock lock(m_pLock);

+        m_dRateSeeking = dRate;

+    }

+    return ChangeRate();

+}

+

+HRESULT CSourceSeeking::GetRate( __out double * pdRate)

+{

+    CheckPointer(pdRate, E_POINTER);

+    CAutoLock lock(m_pLock);

+    *pdRate = m_dRateSeeking;

+    return S_OK;

+}

+

+HRESULT CSourceSeeking::GetPreroll(__out LONGLONG *pPreroll)

+{

+    CheckPointer(pPreroll, E_POINTER);

+    *pPreroll = 0;

+    return S_OK;

+}

+

+

+

+

+

+// --- CSourcePosition implementation ----------

+

+

+CSourcePosition::CSourcePosition(__in_opt LPCTSTR pName,

+				 __in_opt LPUNKNOWN pUnk,

+				 __inout HRESULT* phr,

+				 __in CCritSec * pLock) :

+    CMediaPosition(pName, pUnk),

+    m_pLock(pLock),

+    m_Start(CRefTime((LONGLONG)0))

+{

+    m_Stop = _I64_MAX;

+    m_Rate = 1.0;

+}

+

+

+STDMETHODIMP

+CSourcePosition::get_Duration(__out REFTIME * plength)

+{

+    CheckPointer(plength,E_POINTER);

+    ValidateReadWritePtr(plength,sizeof(REFTIME));

+    CAutoLock lock(m_pLock);

+

+    *plength = m_Duration;

+    return S_OK;

+}

+

+

+STDMETHODIMP

+CSourcePosition::put_CurrentPosition(REFTIME llTime)

+{

+    m_pLock->Lock();

+    m_Start = llTime;

+    m_pLock->Unlock();

+

+    return ChangeStart();

+}

+

+

+STDMETHODIMP

+CSourcePosition::get_StopTime(__out REFTIME * pllTime)

+{

+    CheckPointer(pllTime,E_POINTER);

+    ValidateReadWritePtr(pllTime,sizeof(REFTIME));

+    CAutoLock lock(m_pLock);

+

+    *pllTime = m_Stop;

+    return S_OK;

+}

+

+

+STDMETHODIMP

+CSourcePosition::put_StopTime(REFTIME llTime)

+{

+    m_pLock->Lock();

+    m_Stop = llTime;

+    m_pLock->Unlock();

+

+    return ChangeStop();

+}

+

+

+STDMETHODIMP

+CSourcePosition::get_PrerollTime(__out REFTIME * pllTime)

+{

+    CheckPointer(pllTime,E_POINTER);

+    ValidateReadWritePtr(pllTime,sizeof(REFTIME));

+    return E_NOTIMPL;

+}

+

+

+STDMETHODIMP

+CSourcePosition::put_PrerollTime(REFTIME llTime)

+{

+    return E_NOTIMPL;

+}

+

+

+STDMETHODIMP

+CSourcePosition::get_Rate(__out double * pdRate)

+{

+    CheckPointer(pdRate,E_POINTER);

+    ValidateReadWritePtr(pdRate,sizeof(double));

+    CAutoLock lock(m_pLock);

+

+    *pdRate = m_Rate;

+    return S_OK;

+}

+

+

+STDMETHODIMP

+CSourcePosition::put_Rate(double dRate)

+{

+    m_pLock->Lock();

+    m_Rate = dRate;

+    m_pLock->Unlock();

+

+    return ChangeRate();

+}

+

+

+// By default we can seek forwards

+

+STDMETHODIMP

+CSourcePosition::CanSeekForward(__out LONG *pCanSeekForward)

+{

+    CheckPointer(pCanSeekForward,E_POINTER);

+    *pCanSeekForward = OATRUE;

+    return S_OK;

+}

+

+

+// By default we can seek backwards

+

+STDMETHODIMP

+CSourcePosition::CanSeekBackward(__out LONG *pCanSeekBackward)

+{

+    CheckPointer(pCanSeekBackward,E_POINTER);

+    *pCanSeekBackward = OATRUE;

+    return S_OK;

+}

+

+

+// --- Implementation of CBasicAudio class ----------

+

+

+CBasicAudio::CBasicAudio(__in_opt LPCTSTR pName,__in_opt LPUNKNOWN punk) :

+    CUnknown(pName, punk)

+{

+}

+

+// overriden to publicise our interfaces

+

+STDMETHODIMP

+CBasicAudio::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv)

+{

+    ValidateReadWritePtr(ppv,sizeof(PVOID));

+    if (riid == IID_IBasicAudio) {

+	return GetInterface( (IBasicAudio *) this, ppv);

+    } else {

+	return CUnknown::NonDelegatingQueryInterface(riid, ppv);

+    }

+}

+

+

+STDMETHODIMP

+CBasicAudio::GetTypeInfoCount(__out UINT * pctinfo)

+{

+    return m_basedisp.GetTypeInfoCount(pctinfo);

+}

+

+

+STDMETHODIMP

+CBasicAudio::GetTypeInfo(

+  UINT itinfo,

+  LCID lcid,

+  __deref_out ITypeInfo ** pptinfo)

+{

+    return m_basedisp.GetTypeInfo(

+		IID_IBasicAudio,

+		itinfo,

+		lcid,

+		pptinfo);

+}

+

+

+STDMETHODIMP

+CBasicAudio::GetIDsOfNames(

+  REFIID riid,

+  __in_ecount(cNames) LPOLESTR * rgszNames,

+  UINT cNames,

+  LCID lcid,

+  __out_ecount(cNames) DISPID * rgdispid)

+{

+    return m_basedisp.GetIDsOfNames(

+			IID_IBasicAudio,

+			rgszNames,

+			cNames,

+			lcid,

+			rgdispid);

+}

+

+

+STDMETHODIMP

+CBasicAudio::Invoke(

+  DISPID dispidMember,

+  REFIID riid,

+  LCID lcid,

+  WORD wFlags,

+  __in DISPPARAMS * pdispparams,

+  __out_opt VARIANT * pvarResult,

+  __out_opt EXCEPINFO * pexcepinfo,

+  __out_opt UINT * puArgErr)

+{

+    // this parameter is a dead leftover from an earlier interface

+    if (IID_NULL != riid) {

+	return DISP_E_UNKNOWNINTERFACE;

+    }

+

+    ITypeInfo * pti;

+    HRESULT hr = GetTypeInfo(0, lcid, &pti);

+

+    if (FAILED(hr)) {

+	return hr;

+    }

+

+    hr = pti->Invoke(

+	    (IBasicAudio *)this,

+	    dispidMember,

+	    wFlags,

+	    pdispparams,

+	    pvarResult,

+	    pexcepinfo,

+	    puArgErr);

+

+    pti->Release();

+    return hr;

+}

+

+

+// --- IVideoWindow implementation ----------

+

+CBaseVideoWindow::CBaseVideoWindow(__in_opt LPCTSTR pName,__in_opt LPUNKNOWN punk) :

+    CUnknown(pName, punk)

+{

+}

+

+

+// overriden to publicise our interfaces

+

+STDMETHODIMP

+CBaseVideoWindow::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv)

+{

+    ValidateReadWritePtr(ppv,sizeof(PVOID));

+    if (riid == IID_IVideoWindow) {

+	return GetInterface( (IVideoWindow *) this, ppv);

+    } else {

+	return CUnknown::NonDelegatingQueryInterface(riid, ppv);

+    }

+}

+

+

+STDMETHODIMP

+CBaseVideoWindow::GetTypeInfoCount(__out UINT * pctinfo)

+{

+    return m_basedisp.GetTypeInfoCount(pctinfo);

+}

+

+

+STDMETHODIMP

+CBaseVideoWindow::GetTypeInfo(

+  UINT itinfo,

+  LCID lcid,

+  __deref_out ITypeInfo ** pptinfo)

+{

+    return m_basedisp.GetTypeInfo(

+		IID_IVideoWindow,

+		itinfo,

+		lcid,

+		pptinfo);

+}

+

+

+STDMETHODIMP

+CBaseVideoWindow::GetIDsOfNames(

+  REFIID riid,

+  __in_ecount(cNames) LPOLESTR * rgszNames,

+  UINT cNames,

+  LCID lcid,

+  __out_ecount(cNames) DISPID * rgdispid)

+{

+    return m_basedisp.GetIDsOfNames(

+			IID_IVideoWindow,

+			rgszNames,

+			cNames,

+			lcid,

+			rgdispid);

+}

+

+

+STDMETHODIMP

+CBaseVideoWindow::Invoke(

+  DISPID dispidMember,

+  REFIID riid,

+  LCID lcid,

+  WORD wFlags,

+  __in DISPPARAMS * pdispparams,

+  __out_opt VARIANT * pvarResult,

+  __out_opt EXCEPINFO * pexcepinfo,

+  __out_opt UINT * puArgErr)

+{

+    // this parameter is a dead leftover from an earlier interface

+    if (IID_NULL != riid) {

+	return DISP_E_UNKNOWNINTERFACE;

+    }

+

+    ITypeInfo * pti;

+    HRESULT hr = GetTypeInfo(0, lcid, &pti);

+

+    if (FAILED(hr)) {

+	return hr;

+    }

+

+    hr = pti->Invoke(

+	    (IVideoWindow *)this,

+	    dispidMember,

+	    wFlags,

+	    pdispparams,

+	    pvarResult,

+	    pexcepinfo,

+	    puArgErr);

+

+    pti->Release();

+    return hr;

+}

+

+

+// --- IBasicVideo implementation ----------

+

+

+CBaseBasicVideo::CBaseBasicVideo(__in_opt LPCTSTR pName,__in_opt LPUNKNOWN punk) :

+    CUnknown(pName, punk)

+{

+}

+

+

+// overriden to publicise our interfaces

+

+STDMETHODIMP

+CBaseBasicVideo::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv)

+{

+    ValidateReadWritePtr(ppv,sizeof(PVOID));

+    if (riid == IID_IBasicVideo || riid == IID_IBasicVideo2) {

+	return GetInterface( static_cast<IBasicVideo2 *>(this), ppv);

+    } else {

+	return CUnknown::NonDelegatingQueryInterface(riid, ppv);

+    }

+}

+

+

+STDMETHODIMP

+CBaseBasicVideo::GetTypeInfoCount(__out UINT * pctinfo)

+{

+    return m_basedisp.GetTypeInfoCount(pctinfo);

+}

+

+

+STDMETHODIMP

+CBaseBasicVideo::GetTypeInfo(

+  UINT itinfo,

+  LCID lcid,

+  __deref_out ITypeInfo ** pptinfo)

+{

+    return m_basedisp.GetTypeInfo(

+		IID_IBasicVideo,

+		itinfo,

+		lcid,

+		pptinfo);

+}

+

+

+STDMETHODIMP

+CBaseBasicVideo::GetIDsOfNames(

+  REFIID riid,

+  __in_ecount(cNames) LPOLESTR * rgszNames,

+  UINT cNames,

+  LCID lcid,

+  __out_ecount(cNames) DISPID * rgdispid)

+{

+    return m_basedisp.GetIDsOfNames(

+			IID_IBasicVideo,

+			rgszNames,

+			cNames,

+			lcid,

+			rgdispid);

+}

+

+

+STDMETHODIMP

+CBaseBasicVideo::Invoke(

+  DISPID dispidMember,

+  REFIID riid,

+  LCID lcid,

+  WORD wFlags,

+  __in DISPPARAMS * pdispparams,

+  __out_opt VARIANT * pvarResult,

+  __out_opt EXCEPINFO * pexcepinfo,

+  __out_opt UINT * puArgErr)

+{

+    // this parameter is a dead leftover from an earlier interface

+    if (IID_NULL != riid) {

+	return DISP_E_UNKNOWNINTERFACE;

+    }

+

+    ITypeInfo * pti;

+    HRESULT hr = GetTypeInfo(0, lcid, &pti);

+

+    if (FAILED(hr)) {

+	return hr;

+    }

+

+    hr = pti->Invoke(

+	    (IBasicVideo *)this,

+	    dispidMember,

+	    wFlags,

+	    pdispparams,

+	    pvarResult,

+	    pexcepinfo,

+	    puArgErr);

+

+    pti->Release();

+    return hr;

+}

+

+

+// --- Implementation of Deferred Commands ----------

+

+

+CDispParams::CDispParams(UINT nArgs, __in_ecount(nArgs) VARIANT* pArgs, __inout_opt HRESULT *phr)

+{

+   cNamedArgs = 0;

+   rgdispidNamedArgs = NULL;

+   cArgs = nArgs;

+

+    if (cArgs) {

+	rgvarg = new VARIANT[cArgs];

+        if (NULL == rgvarg) {

+            cArgs = 0;

+            if (phr) {

+                *phr = E_OUTOFMEMORY;

+            }

+            return;

+        }

+

+	for (UINT i = 0; i < cArgs; i++) {

+

+            //  Why aren't we using VariantCopy?

+

+	    VARIANT * pDest = &rgvarg[i];

+	    VARIANT * pSrc = &pArgs[i];

+

+	    pDest->vt = pSrc->vt;

+	    switch(pDest->vt) {

+

+	    case VT_I4:

+		pDest->lVal = pSrc->lVal;

+		break;

+

+	    case VT_UI1:

+		pDest->bVal = pSrc->bVal;

+		break;

+

+	    case VT_I2:

+		pDest->iVal = pSrc->iVal;

+		break;

+

+	    case VT_R4:

+		pDest->fltVal = pSrc->fltVal;

+		break;

+

+	    case VT_R8:

+		pDest->dblVal = pSrc->dblVal;

+		break;

+

+	    case VT_BOOL:

+		pDest->boolVal = pSrc->boolVal;

+		break;

+

+	    case VT_ERROR:

+		pDest->scode = pSrc->scode;

+		break;

+

+	    case VT_CY:

+		pDest->cyVal = pSrc->cyVal;

+		break;

+

+	    case VT_DATE:

+		pDest->date = pSrc->date;

+		break;

+

+	    case VT_BSTR:

+		if ((PVOID)pSrc->bstrVal == NULL) {

+		    pDest->bstrVal = NULL;

+		} else {

+

+		    // a BSTR is a WORD followed by a UNICODE string.

+		    // the pointer points just after the WORD

+

+		    WORD len = * (WORD*) (pSrc->bstrVal - (sizeof(WORD) / sizeof(OLECHAR)));

+		    OLECHAR* pch = new OLECHAR[len + (sizeof(WORD)/sizeof(OLECHAR))];

+                    if (pch) {

+        		WORD *pui = (WORD*)pch;

+        		*pui = len;

+         	        pDest->bstrVal = pch + (sizeof(WORD)/sizeof(OLECHAR));

+         		CopyMemory(pDest->bstrVal, pSrc->bstrVal, len*sizeof(OLECHAR));

+                    } else {

+                        cArgs = i;

+                        if (phr) {

+                            *phr = E_OUTOFMEMORY;

+                        }

+                    }

+		}

+		break;

+

+	    case VT_UNKNOWN:

+		pDest->punkVal = pSrc->punkVal;

+		pDest->punkVal->AddRef();

+		break;

+

+	    case VT_DISPATCH:

+		pDest->pdispVal = pSrc->pdispVal;

+		pDest->pdispVal->AddRef();

+		break;

+

+	    default:

+		// a type we haven't got round to adding yet!

+		ASSERT(0);

+		break;

+	    }

+	}

+

+    } else {

+	rgvarg = NULL;

+    }

+

+}

+

+

+CDispParams::~CDispParams()

+{

+    for (UINT i = 0; i < cArgs; i++) {

+	switch(rgvarg[i].vt) {

+        case VT_BSTR:

+            //  Explicitly cast BSTR to PVOID to tell code scanning tools we really mean to test the pointer

+	    if ((PVOID)rgvarg[i].bstrVal != NULL) {

+		OLECHAR * pch = rgvarg[i].bstrVal - (sizeof(WORD)/sizeof(OLECHAR));

+		delete pch;

+	    }

+	    break;

+

+	case VT_UNKNOWN:

+	    rgvarg[i].punkVal->Release();

+	    break;

+

+	case VT_DISPATCH:

+	    rgvarg[i].pdispVal->Release();

+	    break;

+	}

+    }

+    delete[] rgvarg;

+}

+

+

+// lifetime is controlled by refcounts (see defer.h)

+

+CDeferredCommand::CDeferredCommand(

+    __inout CCmdQueue * pQ,

+    __in_opt LPUNKNOWN	pUnk,

+    __inout HRESULT *	phr,

+    __in LPUNKNOWN	pUnkExecutor,

+    REFTIME	time,

+    __in GUID*	iid,

+    long	dispidMethod,

+    short	wFlags,

+    long	nArgs,

+    __in_ecount(nArgs) VARIANT*	pDispParams,

+    __out VARIANT*	pvarResult,

+    __out short*	puArgErr,

+    BOOL	bStream

+    ) :

+	CUnknown(NAME("DeferredCommand"), pUnk),

+	m_pQueue(pQ),

+	m_pUnk(pUnkExecutor),

+	m_iid(iid),

+	m_dispidMethod(dispidMethod),

+	m_wFlags(wFlags),

+	m_DispParams(nArgs, pDispParams, phr),

+	m_pvarResult(pvarResult),

+	m_bStream(bStream),

+	m_hrResult(E_ABORT)

+

+{

+    // convert REFTIME to REFERENCE_TIME

+    COARefTime convertor(time);

+    m_time = convertor;

+

+    // no check of time validity - it's ok to queue a command that's

+    // already late

+

+    // check iid is supportable on pUnk by QueryInterface for it

+    IUnknown * pInterface;

+    HRESULT hr = m_pUnk->QueryInterface(GetIID(), (void**) &pInterface);

+    if (FAILED(hr)) {

+	*phr = hr;

+	return;

+    }

+    pInterface->Release();

+

+

+    // !!! check dispidMethod and param/return types using typelib

+    ITypeInfo *pti;

+    hr = m_Dispatch.GetTypeInfo(*iid, 0, 0, &pti);

+    if (FAILED(hr)) {

+	*phr = hr;

+	return;

+    }

+    // !!! some sort of ITypeInfo validity check here

+    pti->Release();

+

+

+    // Fix up the dispid for put and get

+    if (wFlags == DISPATCH_PROPERTYPUT) {

+        m_DispParams.cNamedArgs = 1;

+        m_DispId = DISPID_PROPERTYPUT;

+        m_DispParams.rgdispidNamedArgs = &m_DispId;

+    }

+

+    // all checks ok - add to queue

+    hr = pQ->Insert(this);

+    if (FAILED(hr)) {

+	*phr = hr;

+    }

+}

+

+

+// refcounts are held by caller of InvokeAt... and by list. So if

+// we get here, we can't be on the list

+

+#if 0

+CDeferredCommand::~CDeferredCommand()

+{

+    // this assert is invalid since if the queue is deleted while we are

+    // still on the queue, we will have been removed by the queue and this

+    // m_pQueue will not have been modified.

+    // ASSERT(m_pQueue == NULL);

+

+    // we don't hold a ref count on pUnk, which is the object that should

+    // execute the command.

+    // This is because there would otherwise be a circular refcount problem

+    // since pUnk probably owns the CmdQueue object that has a refcount

+    // on us.

+    // The lifetime of pUnk is guaranteed by it being part of, or lifetime

+    // controlled by, our parent object. As long as we are on the list, pUnk

+    // must be valid. Once we are off the list, we do not use pUnk.

+

+}

+#endif

+

+

+// overriden to publicise our interfaces

+

+STDMETHODIMP

+CDeferredCommand::NonDelegatingQueryInterface(REFIID riid, __out void **ppv)

+{

+    ValidateReadWritePtr(ppv,sizeof(PVOID));

+    if (riid == IID_IDeferredCommand) {

+	return GetInterface( (IDeferredCommand *) this, ppv);

+    } else {

+	return CUnknown::NonDelegatingQueryInterface(riid, ppv);

+    }

+}

+

+

+// remove from q. this will reduce the refcount by one (since the q

+// holds a count) but can't make us go away since he must have a

+// refcount in order to call this method.

+

+STDMETHODIMP

+CDeferredCommand::Cancel()

+{

+    if (m_pQueue == NULL) {

+	return VFW_E_ALREADY_CANCELLED;

+    }

+

+    HRESULT hr = m_pQueue->Remove(this);

+    if (FAILED(hr)) {

+	return hr;

+    }

+

+    m_pQueue = NULL;

+    return S_OK;

+}

+

+

+STDMETHODIMP

+CDeferredCommand::Confidence(__out LONG* pConfidence)

+{

+    return E_NOTIMPL;

+}

+

+

+STDMETHODIMP

+CDeferredCommand::GetHResult(__out HRESULT * phrResult)

+{

+    CheckPointer(phrResult,E_POINTER);

+    ValidateReadWritePtr(phrResult,sizeof(HRESULT));

+

+    if (m_pQueue != NULL) {

+	return E_ABORT;

+    }

+    *phrResult = m_hrResult;

+    return S_OK;

+}

+

+

+// set the time to be a new time (checking that it is valid) and

+// then requeue

+

+STDMETHODIMP

+CDeferredCommand::Postpone(REFTIME newtime)

+{

+

+    // check that this time is not past

+    // convert REFTIME to REFERENCE_TIME

+    COARefTime convertor(newtime);

+

+    // check that the time has not passed

+    if (m_pQueue->CheckTime(convertor, IsStreamTime())) {

+	return VFW_E_TIME_ALREADY_PASSED;

+    }

+

+    // extract from list

+    HRESULT hr = m_pQueue->Remove(this);

+    if (FAILED(hr)) {

+	return hr;

+    }

+

+    // change time

+    m_time = convertor;

+

+    // requeue

+    hr = m_pQueue->Insert(this);

+

+    return hr;

+}

+

+

+HRESULT

+CDeferredCommand::Invoke()

+{

+    // check that we are still outstanding

+    if (m_pQueue == NULL) {

+	return VFW_E_ALREADY_CANCELLED;

+    }

+

+    // get the type info

+    ITypeInfo* pti;

+    HRESULT hr = m_Dispatch.GetTypeInfo(GetIID(), 0, 0, &pti);

+    if (FAILED(hr)) {

+	return hr;

+    }

+

+    // qi for the expected interface and then invoke it. Note that we have to

+    // treat the returned interface as IUnknown since we don't know its type.

+    IUnknown* pInterface;

+

+    hr = m_pUnk->QueryInterface(GetIID(), (void**) &pInterface);

+    if (FAILED(hr)) {

+	pti->Release();

+	return hr;

+    }

+

+    EXCEPINFO expinfo;

+    UINT uArgErr;

+    m_hrResult = pti->Invoke(

+	pInterface,

+	GetMethod(),

+	GetFlags(),

+	GetParams(),

+	GetResult(),

+	&expinfo,

+	&uArgErr);

+

+    // release the interface we QI'd for

+    pInterface->Release();

+    pti->Release();

+

+

+    // remove from list whether or not successful

+    // or we loop indefinitely

+    hr = m_pQueue->Remove(this);

+    m_pQueue = NULL;

+    return hr;

+}

+

+

+

+// --- CCmdQueue methods ----------

+

+

+CCmdQueue::CCmdQueue(__inout_opt HRESULT *phr) :

+    m_listPresentation(NAME("Presentation time command list")),

+    m_listStream(NAME("Stream time command list")),

+    m_evDue(TRUE, phr),    // manual reset

+    m_dwAdvise(0),

+    m_pClock(NULL),

+    m_bRunning(FALSE)

+{

+}

+

+

+CCmdQueue::~CCmdQueue()

+{

+    // empty all our lists

+

+    // we hold a refcount on each, so traverse and Release each

+    // entry then RemoveAll to empty the list

+    POSITION pos = m_listPresentation.GetHeadPosition();

+

+    while(pos) {

+	CDeferredCommand* pCmd = m_listPresentation.GetNext(pos);

+	pCmd->Release();

+    }

+    m_listPresentation.RemoveAll();

+

+    pos = m_listStream.GetHeadPosition();

+

+    while(pos) {

+	CDeferredCommand* pCmd = m_listStream.GetNext(pos);

+	pCmd->Release();

+    }

+    m_listStream.RemoveAll();

+

+    if (m_pClock) {

+	if (m_dwAdvise) {

+	    m_pClock->Unadvise(m_dwAdvise);

+	    m_dwAdvise = 0;

+	}

+	m_pClock->Release();

+    }

+}

+

+

+// returns a new CDeferredCommand object that will be initialised with

+// the parameters and will be added to the queue during construction.

+// returns S_OK if successfully created otherwise an error and

+// no object has been queued.

+

+HRESULT

+CCmdQueue::New(

+    __out CDeferredCommand **ppCmd,

+    __in     LPUNKNOWN	pUnk,		// this object will execute command

+    REFTIME	time,

+    __in GUID*	iid,

+    long	dispidMethod,

+    short	wFlags,

+    long	cArgs,

+    __in_ecount(cArgs) VARIANT*	pDispParams,

+    __out VARIANT*	pvarResult,

+    __out short*	puArgErr,

+    BOOL	bStream

+)

+{

+    CAutoLock lock(&m_Lock);

+

+    HRESULT hr = S_OK;

+    *ppCmd = NULL;

+

+    CDeferredCommand* pCmd;

+    pCmd = new CDeferredCommand(

+		    this,

+		    NULL,	    // not aggregated

+		    &hr,

+		    pUnk,	    // this guy will execute

+		    time,

+		    iid,

+		    dispidMethod,

+		    wFlags,

+		    cArgs,

+		    pDispParams,

+		    pvarResult,

+		    puArgErr,

+		    bStream);

+

+    if (pCmd == NULL) {

+	hr = E_OUTOFMEMORY;

+    } else {

+	*ppCmd = pCmd;

+    }

+    return hr;

+}

+

+

+HRESULT

+CCmdQueue::Insert(__in CDeferredCommand* pCmd)

+{

+    CAutoLock lock(&m_Lock);

+

+    // addref the item

+    pCmd->AddRef();

+

+    CGenericList<CDeferredCommand> * pList;

+    if (pCmd->IsStreamTime()) {

+	pList = &m_listStream;

+    } else {

+	pList = &m_listPresentation;

+    }

+    POSITION pos = pList->GetHeadPosition();

+

+    // seek past all items that are before us

+    while (pos &&

+	(pList->GetValid(pos)->GetTime() <= pCmd->GetTime())) {

+

+	pList->GetNext(pos);

+    }

+

+    // now at end of list or in front of items that come later

+    if (!pos) {

+	pList->AddTail(pCmd);

+    } else {

+	pList->AddBefore(pos, pCmd);

+    }

+

+    SetTimeAdvise();

+    return S_OK;

+}

+

+

+HRESULT

+CCmdQueue::Remove(__in CDeferredCommand* pCmd)

+{

+    CAutoLock lock(&m_Lock);

+    HRESULT hr = S_OK;

+

+    CGenericList<CDeferredCommand> * pList;

+    if (pCmd->IsStreamTime()) {

+	pList = &m_listStream;

+    } else {

+	pList = &m_listPresentation;

+    }

+    POSITION pos = pList->GetHeadPosition();

+

+    // traverse the list

+    while (pos && (pList->GetValid(pos) != pCmd)) {

+	pList->GetNext(pos);

+    }

+

+    // did we drop off the end?

+    if (!pos) {

+	hr = VFW_E_NOT_FOUND;

+    } else {

+

+	// found it - now take off list

+	pList->Remove(pos);

+

+	// Insert did an AddRef, so release it

+	pCmd->Release();

+

+	// check that timer request is still for earliest time

+	SetTimeAdvise();

+    }

+    return hr;

+}

+

+

+// set the clock used for timing

+

+HRESULT

+CCmdQueue::SetSyncSource(__in_opt IReferenceClock* pClock)

+{

+    CAutoLock lock(&m_Lock);

+

+    // addref the new clock first in case they are the same

+    if (pClock) {

+	pClock->AddRef();

+    }

+

+    // kill any advise on the old clock

+    if (m_pClock) {

+	if (m_dwAdvise) {

+	    m_pClock->Unadvise(m_dwAdvise);

+	    m_dwAdvise = 0;

+	}

+	m_pClock->Release();

+    }

+    m_pClock = pClock;

+

+    // set up a new advise

+    SetTimeAdvise();

+    return S_OK;

+}

+

+

+// set up a timer event with the reference clock

+

+void

+CCmdQueue::SetTimeAdvise(void)

+{

+    // make sure we have a clock to use

+    if (!m_pClock) {

+	return;

+    }

+

+    // reset the event whenever we are requesting a new signal

+    m_evDue.Reset();

+

+    // time 0 is earliest

+    CRefTime current;

+

+    // find the earliest presentation time

+    POSITION pos = m_listPresentation.GetHeadPosition();

+    if (pos != NULL) {

+	current = m_listPresentation.GetValid(pos)->GetTime();

+    }

+

+    // if we're running, check the stream times too

+    if (m_bRunning) {

+

+	CRefTime t;

+        pos = m_listStream.GetHeadPosition();

+	if (NULL != pos) {

+	    t = m_listStream.GetValid(pos)->GetTime();

+

+	    // add on stream time offset to get presentation time

+	    t += m_StreamTimeOffset;

+

+	    // is this earlier?

+	    if ((current == TimeZero) || (t < current)) {

+		current = t;

+	    }

+	}

+    }

+

+    // need to change?

+    if ((current > TimeZero) && (current != m_tCurrentAdvise)) {

+	if (m_dwAdvise) {

+	    m_pClock->Unadvise(m_dwAdvise);

+	    // reset the event whenever we are requesting a new signal

+	    m_evDue.Reset();

+	}

+

+	// ask for time advice - the first two params are either

+	// stream time offset and stream time or

+	// presentation time and 0. we always use the latter

+	HRESULT hr = m_pClock->AdviseTime(

+		    (REFERENCE_TIME)current,

+		    TimeZero,

+		    (HEVENT) HANDLE(m_evDue),

+		    &m_dwAdvise);

+

+	ASSERT(SUCCEEDED(hr));

+	m_tCurrentAdvise = current;

+    }

+}

+

+

+// switch to run mode. Streamtime to Presentation time mapping known.

+

+HRESULT

+CCmdQueue::Run(REFERENCE_TIME tStreamTimeOffset)

+{

+    CAutoLock lock(&m_Lock);

+

+    m_StreamTimeOffset = tStreamTimeOffset;

+    m_bRunning = TRUE;

+

+    // ensure advise is accurate

+    SetTimeAdvise();

+    return S_OK;

+}

+

+

+// switch to Stopped or Paused mode. Time mapping not known.

+

+HRESULT

+CCmdQueue::EndRun()

+{

+    CAutoLock lock(&m_Lock);

+

+    m_bRunning = FALSE;

+

+    // check timer setting - stream times

+    SetTimeAdvise();

+    return S_OK;

+}

+

+

+// return a pointer to the next due command. Blocks for msTimeout

+// milliseconds until there is a due command.

+// Stream-time commands will only become due between Run and Endrun calls.

+// The command remains queued until invoked or cancelled.

+// Returns E_ABORT if timeout occurs, otherwise S_OK (or other error).

+//

+// returns an AddRef'd object

+

+HRESULT

+CCmdQueue::GetDueCommand(__out CDeferredCommand ** ppCmd, long msTimeout)

+{

+    // loop until we timeout or find a due command

+    for (;;) {

+

+	{

+	    CAutoLock lock(&m_Lock);

+

+

+	    // find the earliest command

+	    CDeferredCommand * pCmd = NULL;

+

+	    // check the presentation time and the

+	    // stream time list to find the earliest

+

+            POSITION pos = m_listPresentation.GetHeadPosition();

+

+	    if (NULL != pos) {

+		pCmd = m_listPresentation.GetValid(pos);

+	    }

+

+	    if (m_bRunning) {

+		pos = m_listStream.GetHeadPosition();

+                if (NULL != pos) {

+                    CDeferredCommand* pStrm = m_listStream.GetValid(pos);

+

+                    CRefTime t = pStrm->GetTime() + m_StreamTimeOffset;

+                    if (!pCmd || (t < pCmd->GetTime())) {

+                        pCmd = pStrm;

+                    }

+                }

+            }

+

+	    //	if we have found one, is it due?

+	    if (pCmd) {

+		if (CheckTime(pCmd->GetTime(), pCmd->IsStreamTime())) {

+

+		    // yes it's due - addref it

+		    pCmd->AddRef();

+		    *ppCmd = pCmd;

+		    return S_OK;

+		}

+	    }

+	}

+

+	// block until the advise is signalled

+	if (WaitForSingleObject(m_evDue, msTimeout) != WAIT_OBJECT_0) {

+	    return E_ABORT;

+	}

+    }

+}

+

+

+// return a pointer to a command that will be due for a given time.

+// Pass in a stream time here. The stream time offset will be passed

+// in via the Run method.

+// Commands remain queued until invoked or cancelled.

+// This method will not block. It will report E_ABORT if there are no

+// commands due yet.

+//

+// returns an AddRef'd object

+

+HRESULT

+CCmdQueue::GetCommandDueFor(REFERENCE_TIME rtStream, __out CDeferredCommand**ppCmd)

+{

+    CAutoLock lock(&m_Lock);

+

+    CRefTime tStream(rtStream);

+

+    // find the earliest stream and presentation time commands

+    CDeferredCommand* pStream = NULL;

+    POSITION pos = m_listStream.GetHeadPosition();

+    if (NULL != pos) {

+	pStream = m_listStream.GetValid(pos);

+    }

+    CDeferredCommand* pPresent = NULL;

+    pos = m_listPresentation.GetHeadPosition();

+    if (NULL != pos) {

+	pPresent = m_listPresentation.GetValid(pos);

+    }

+

+    // is there a presentation time that has passed already

+    if (pPresent && CheckTime(pPresent->GetTime(), FALSE)) {

+	pPresent->AddRef();

+	*ppCmd = pPresent;

+	return S_OK;

+    }

+

+    // is there a stream time command due before this stream time

+    if (pStream && (pStream->GetTime() <= tStream)) {

+	pStream->AddRef();

+	*ppCmd = pStream;

+	return S_OK;

+    }

+

+    // if we are running, we can map presentation times to

+    // stream time. In this case, is there a presentation time command

+    // that will be due before this stream time is presented?

+    if (m_bRunning && pPresent) {

+

+	// this stream time will appear at...

+	tStream += m_StreamTimeOffset;

+

+	// due before that?

+	if (pPresent->GetTime() <= tStream) {

+	    *ppCmd = pPresent;

+	    return S_OK;

+	}

+    }

+

+    // no commands due yet

+    return VFW_E_NOT_FOUND;

+}

+