/*
 * Implements ACM Wrapper(CLSID_ACMWrapper).
 *
 *	FIXME - stub
 *	FIXME - no encoding
 *
 * Copyright (C) 2002 Hidenori TAKESHIMA <hidenori@a2.ctktv.ne.jp>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */


#include "config.h"

#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winuser.h"
#include "winerror.h"
#include "msacm.h"
#include "strmif.h"
#include "control.h"
#include "amvideo.h"
#include "vfwmsgs.h"
#include "uuids.h"

#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(quartz);

#include "quartz_private.h"
#include "xform.h"
#include "mtype.h"


static const WCHAR ACMWrapper_FilterName[] =
{'A','C','M',' ','W','r','a','p','p','e','r',0};


typedef struct CACMWrapperImpl
{
	HACMSTREAM	has;
	WAVEFORMATEX*	pwfxIn;
	AM_MEDIA_TYPE*	pmtOuts;
	DWORD		cOuts;
	BYTE*		pConvBuf;
	DWORD		cbConvBlockSize;
	DWORD		cbConvCached;
	DWORD		cbConvAllocated;
} CACMWrapperImpl;


static
void ACMWrapper_CleanupMTypes( CACMWrapperImpl* This )
{
	DWORD	n;

	if ( This->pmtOuts == NULL ) return;
	for ( n = 0; n < This->cOuts; n++ )
	{
		QUARTZ_MediaType_Free( &This->pmtOuts[n] );
	}
	QUARTZ_FreeMem( This->pmtOuts );
	This->pmtOuts = NULL;
	This->cOuts = 0;
}

static
void ACMWrapper_CleanupConvBuf( CACMWrapperImpl* This )
{
	if ( This->pConvBuf != NULL )
	{
		QUARTZ_FreeMem( This->pConvBuf );
		This->pConvBuf = NULL;
	}
	This->cbConvBlockSize = 0;
	This->cbConvCached = 0;
	This->cbConvAllocated = 0;
}

static
const WAVEFORMATEX* ACMWrapper_GetAudioFmt( const AM_MEDIA_TYPE* pmt )
{
	const WAVEFORMATEX*	pwfx;

	if ( !IsEqualGUID( &pmt->majortype, &MEDIATYPE_Audio ) )
		return NULL;
	if ( !IsEqualGUID( &pmt->subtype, &MEDIASUBTYPE_NULL ) &&
		 !QUARTZ_MediaSubType_IsFourCC( &pmt->subtype ) )
		return NULL;
	if ( !IsEqualGUID( &pmt->formattype, &FORMAT_WaveFormatEx ) )
		return NULL;
	if ( pmt->pbFormat == NULL ||
		 pmt->cbFormat < (sizeof(WAVEFORMATEX)-sizeof(WORD)) )
		return NULL;

	pwfx = (const WAVEFORMATEX*)pmt->pbFormat;

	if ( pwfx->wFormatTag != 1 && pmt->cbFormat < sizeof(WAVEFORMATEX) )
		return NULL;

	return pwfx;
}

static
HRESULT ACMWrapper_SetupAudioFmt(
	AM_MEDIA_TYPE* pmt,
	DWORD cbFormat, WORD wFormatTag, DWORD dwBlockAlign )
{
	ZeroMemory( pmt, sizeof(AM_MEDIA_TYPE) );
	memcpy( &pmt->majortype, &MEDIATYPE_Audio, sizeof(GUID) );
	QUARTZ_MediaSubType_FromFourCC( &pmt->subtype, (DWORD)wFormatTag );
	pmt->bFixedSizeSamples = 1;
	pmt->bTemporalCompression = 1;
	pmt->lSampleSize = dwBlockAlign;
	memcpy( &pmt->formattype, &FORMAT_WaveFormatEx, sizeof(GUID) );
	pmt->pUnk = NULL;
	pmt->cbFormat = cbFormat;
	pmt->pbFormat = (BYTE*)CoTaskMemAlloc( cbFormat );
	if ( pmt->pbFormat == NULL )
		return E_OUTOFMEMORY;

	return S_OK;
}

static
void ACMWrapper_FillFmtPCM(
	WAVEFORMATEX* pwfxOut,
	const WAVEFORMATEX* pwfxIn,
	WORD wBitsPerSampOut )
{
	pwfxOut->wFormatTag = 1;
	pwfxOut->nChannels = pwfxIn->nChannels;
	pwfxOut->nSamplesPerSec = pwfxIn->nSamplesPerSec;
	pwfxOut->nAvgBytesPerSec = ((DWORD)pwfxIn->nSamplesPerSec * (DWORD)pwfxIn->nChannels * (DWORD)wBitsPerSampOut) >> 3;
	pwfxOut->nBlockAlign = (pwfxIn->nChannels * wBitsPerSampOut) >> 3;
	pwfxOut->wBitsPerSample = wBitsPerSampOut;
	pwfxOut->cbSize = 0;
}

static
BOOL ACMWrapper_IsSupported(
	WAVEFORMATEX* pwfxOut,
	WAVEFORMATEX* pwfxIn )
{
	MMRESULT	mr;

	mr = acmStreamOpen(
		NULL,(HACMDRIVER)NULL,
		pwfxIn,pwfxOut,NULL,
		0,0,ACM_STREAMOPENF_QUERY);
	if ( mr == ACMERR_NOTPOSSIBLE )
		mr = acmStreamOpen(
			NULL,(HACMDRIVER)NULL,
			pwfxIn,pwfxOut,NULL,
			0,0,ACM_STREAMOPENF_NONREALTIME|ACM_STREAMOPENF_QUERY);
	return !!(mr == MMSYSERR_NOERROR);
}

static
HRESULT ACMWrapper_StreamOpen(
	HACMSTREAM* phas,
	WAVEFORMATEX* pwfxOut,
	WAVEFORMATEX* pwfxIn )
{
	HACMSTREAM	has = (HACMSTREAM)NULL;
	MMRESULT	mr;

	mr = acmStreamOpen(
		&has,(HACMDRIVER)NULL,
		pwfxIn,pwfxOut,NULL,
		0,0,0);
	if ( mr == ACMERR_NOTPOSSIBLE )
		mr = acmStreamOpen(
			&has,(HACMDRIVER)NULL,
			pwfxIn,pwfxOut,NULL,
			0,0,ACM_STREAMOPENF_NONREALTIME);
	if ( mr != MMSYSERR_NOERROR )
	{
		if ( mr == MMSYSERR_NOMEM )
			return E_OUTOFMEMORY;
		return E_FAIL;
	}

	*phas = has;
	return S_OK;
}

/***************************************************************************
 *
 *	CACMWrapperImpl methods
 *
 */

static void ACMWrapper_Close( CACMWrapperImpl* This )
{
	if ( This->has != (HACMSTREAM)NULL )
	{
		acmStreamReset( This->has, 0 );
		acmStreamClose( This->has, 0 );
		This->has = (HACMSTREAM)NULL;
	}
}

static HRESULT ACMWrapper_Init( CTransformBaseImpl* pImpl )
{
	CACMWrapperImpl*	This = pImpl->m_pUserData;

	TRACE("(%p)\n",This);

	if ( This != NULL )
		return NOERROR;

	This = (CACMWrapperImpl*)QUARTZ_AllocMem( sizeof(CACMWrapperImpl) );
	if ( This == NULL )
		return E_OUTOFMEMORY;
	ZeroMemory( This, sizeof(CACMWrapperImpl) );
	pImpl->m_pUserData = This;

	/* construct */
	This->has = (HACMSTREAM)NULL;
	This->pwfxIn = NULL;
	This->pmtOuts = NULL;
	This->cOuts = 0;
	This->pConvBuf = NULL;

	return S_OK;
}

static HRESULT ACMWrapper_Cleanup( CTransformBaseImpl* pImpl )
{
	CACMWrapperImpl*	This = pImpl->m_pUserData;

	TRACE("(%p)\n",This);

	if ( This == NULL )
		return NOERROR;

	/* destruct */
	ACMWrapper_Close( This );
	QUARTZ_FreeMem( This->pwfxIn );
	ACMWrapper_CleanupMTypes( This );
	ACMWrapper_CleanupConvBuf( This );

	QUARTZ_FreeMem( This );
	pImpl->m_pUserData = NULL;

	return S_OK;
}

static HRESULT ACMWrapper_CheckMediaType( CTransformBaseImpl* pImpl, const AM_MEDIA_TYPE* pmtIn, const AM_MEDIA_TYPE* pmtOut )
{
	CACMWrapperImpl*	This = pImpl->m_pUserData;
	const WAVEFORMATEX*	pwfxIn;
	const WAVEFORMATEX*	pwfxOut;
	WAVEFORMATEX	wfx;

	TRACE("(%p)\n",This);
	if ( This == NULL )
		return E_UNEXPECTED;

	pwfxIn = ACMWrapper_GetAudioFmt(pmtIn);
	if ( pwfxIn == NULL ||
	     pwfxIn->wFormatTag == 0 ||
	     pwfxIn->wFormatTag == 1 )
	{
		TRACE("pwfxIn is not a compressed audio\n");
		return E_FAIL;
	}
	if ( pmtOut != NULL )
	{
		pwfxOut = ACMWrapper_GetAudioFmt(pmtOut);
		if ( pwfxOut == NULL || pwfxOut->wFormatTag != 1 )
		{
			TRACE("pwfxOut is not a linear PCM\n");
			return E_FAIL;
		}
		if ( pwfxIn->nChannels != pwfxOut->nChannels ||
		     pwfxIn->nSamplesPerSec != pwfxOut->nSamplesPerSec )
		{
			TRACE("nChannels or nSamplesPerSec is not matched\n");
			return E_FAIL;
		}
		if ( !ACMWrapper_IsSupported((WAVEFORMATEX*)pwfxOut,(WAVEFORMATEX*)pwfxIn) )
		{
			TRACE("specified formats are not supported by ACM\n");
			return E_FAIL;
		}
	}
	else
	{
		ACMWrapper_FillFmtPCM(&wfx,pwfxIn,8);
		if ( ACMWrapper_IsSupported(&wfx,(WAVEFORMATEX*)pwfxIn) )
		{
			TRACE("compressed audio - can be decoded to 8bit\n");
			return S_OK;
		}
		ACMWrapper_FillFmtPCM(&wfx,pwfxIn,16);
		if ( ACMWrapper_IsSupported(&wfx,(WAVEFORMATEX*)pwfxIn) )
		{
			TRACE("compressed audio - can be decoded to 16bit\n");
			return S_OK;
		}

		TRACE("unhandled audio %04x\n",(unsigned)pwfxIn->wFormatTag);
		return E_FAIL;
	}

	return S_OK;
}

static HRESULT ACMWrapper_GetOutputTypes( CTransformBaseImpl* pImpl, const AM_MEDIA_TYPE* pmtIn, const AM_MEDIA_TYPE** ppmtAcceptTypes, ULONG* pcAcceptTypes )
{
	CACMWrapperImpl*	This = pImpl->m_pUserData;
	HRESULT hr;
	const WAVEFORMATEX*	pwfxIn;
	AM_MEDIA_TYPE*		pmtTry;
	WAVEFORMATEX*		pwfxTry;

	FIXME("(%p)\n",This);
	hr = ACMWrapper_CheckMediaType( pImpl, pmtIn, NULL );
	if ( FAILED(hr) )
		return hr;
	pwfxIn = (const WAVEFORMATEX*)pmtIn->pbFormat;

	ACMWrapper_CleanupMTypes( This );
	This->pmtOuts = QUARTZ_AllocMem( sizeof(AM_MEDIA_TYPE) * 2 );
	if ( This->pmtOuts == NULL )
		return E_OUTOFMEMORY;
	This->cOuts = 0;

	pmtTry = &This->pmtOuts[This->cOuts];
	hr = ACMWrapper_SetupAudioFmt(
		pmtTry,
		sizeof(WAVEFORMATEX), 1,
		(pwfxIn->nChannels * 8) >> 3 );
	if ( FAILED(hr) ) goto err;
	pwfxTry = (WAVEFORMATEX*)pmtTry->pbFormat;
	ACMWrapper_FillFmtPCM( pwfxTry, pwfxIn, 8 );
	if ( ACMWrapper_IsSupported( pwfxTry, (WAVEFORMATEX*)pwfxIn ) )
		This->cOuts ++;

        pmtTry = &This->pmtOuts[This->cOuts];
        hr = ACMWrapper_SetupAudioFmt(
                pmtTry,
                sizeof(WAVEFORMATEX), 1,
                (pwfxIn->nChannels * 16) >> 3 );
        if ( FAILED(hr) ) goto err;
        pwfxTry = (WAVEFORMATEX*)pmtTry->pbFormat;
        ACMWrapper_FillFmtPCM( pwfxTry, pwfxIn, 16 );
        if ( ACMWrapper_IsSupported( pwfxTry, (WAVEFORMATEX*)pwfxIn ) )
                This->cOuts ++;

	*ppmtAcceptTypes = This->pmtOuts;
	*pcAcceptTypes = This->cOuts;

	return S_OK;
err:
	ACMWrapper_CleanupMTypes( This );
	return hr;
}

static HRESULT
ACMWrapper_GetConvBufSize(
	CTransformBaseImpl* pImpl,
	CACMWrapperImpl* This,
	DWORD* pcbInput, DWORD* pcbOutput,
	const AM_MEDIA_TYPE* pmtOut, const AM_MEDIA_TYPE* pmtIn )
{
        HRESULT hr;
        const WAVEFORMATEX* pwfxIn;
        const WAVEFORMATEX* pwfxOut;
        HACMSTREAM      has;
        MMRESULT        mr;
        DWORD   cbInput;
        DWORD   cbOutput;

        if ( This == NULL )
                return E_UNEXPECTED;

        hr = ACMWrapper_CheckMediaType( pImpl, pmtIn, pmtOut );
        if ( FAILED(hr) )
                return hr;
        pwfxIn = (const WAVEFORMATEX*)pmtIn->pbFormat;
        pwfxOut = (const WAVEFORMATEX*)pmtOut->pbFormat;

        hr = ACMWrapper_StreamOpen(
                &has, (WAVEFORMATEX*)pwfxOut, (WAVEFORMATEX*)pwfxIn );
        if ( FAILED(hr) )
                return hr;

        cbInput = (pwfxIn->nAvgBytesPerSec + pwfxIn->nBlockAlign - 1) / pwfxIn->nBlockAlign * pwfxIn->nBlockAlign;
        cbOutput = 0;

        mr = acmStreamSize( has, cbInput, &cbOutput, ACM_STREAMSIZEF_SOURCE );
        acmStreamClose( has, 0 );

        if ( mr != MMSYSERR_NOERROR || cbOutput == 0 )
                return E_FAIL;
        TRACE("size %lu -> %lu\n", cbInput, cbOutput);

	if ( pcbInput != NULL ) *pcbInput = cbInput;
	if ( pcbOutput != NULL ) *pcbOutput = cbOutput;

	return S_OK;
}


static HRESULT ACMWrapper_GetAllocProp( CTransformBaseImpl* pImpl, const AM_MEDIA_TYPE* pmtIn, const AM_MEDIA_TYPE* pmtOut, ALLOCATOR_PROPERTIES* pProp, BOOL* pbTransInPlace, BOOL* pbTryToReuseSample )
{
	CACMWrapperImpl*	This = pImpl->m_pUserData;
	HRESULT hr;
	DWORD	cbOutput;

	TRACE("(%p)\n",This);

	if ( This == NULL )
		return E_UNEXPECTED;

	hr = ACMWrapper_GetConvBufSize(
		pImpl, This, NULL, &cbOutput, pmtOut, pmtIn );
	if ( FAILED(hr) )
		return hr;

	pProp->cBuffers = 1;
	pProp->cbBuffer = cbOutput;

	*pbTransInPlace = FALSE;
	*pbTryToReuseSample = FALSE;

	return S_OK;
}

static HRESULT ACMWrapper_BeginTransform( CTransformBaseImpl* pImpl, const AM_MEDIA_TYPE* pmtIn, const AM_MEDIA_TYPE* pmtOut, BOOL bReuseSample )
{
	CACMWrapperImpl*	This = pImpl->m_pUserData;
	HRESULT	hr;
	const WAVEFORMATEX*	pwfxIn;
	const WAVEFORMATEX*	pwfxOut;
	DWORD	cbInput;

	FIXME("(%p,%p,%p,%d)\n",This,pmtIn,pmtOut,bReuseSample);

	if ( This == NULL )
		return E_UNEXPECTED;

	ACMWrapper_Close( This );
	ACMWrapper_CleanupMTypes( This );
	ACMWrapper_CleanupConvBuf( This );

	hr = ACMWrapper_GetConvBufSize(
		pImpl, This, &cbInput, NULL, pmtOut, pmtIn );
        if ( FAILED(hr) )
                return hr;
        pwfxIn = (const WAVEFORMATEX*)pmtIn->pbFormat;
        pwfxOut = (const WAVEFORMATEX*)pmtOut->pbFormat;

	This->pConvBuf = (BYTE*)QUARTZ_AllocMem( cbInput );
	if ( This->pConvBuf == NULL )
		return E_OUTOFMEMORY;
	This->cbConvBlockSize = pwfxIn->nBlockAlign;
	This->cbConvCached = 0;
	This->cbConvAllocated = cbInput;

	hr = ACMWrapper_StreamOpen(
		&This->has,
		(WAVEFORMATEX*)pwfxOut, (WAVEFORMATEX*)pwfxIn );
	if ( FAILED(hr) )
		return E_FAIL;

	return S_OK;
}

static HRESULT ACMWrapper_Convert(
	CTransformBaseImpl* pImpl,
	CACMWrapperImpl* This,
	BYTE* pbSrc, DWORD cbSrcLen,
	DWORD dwConvertFlags,
	REFERENCE_TIME* rtStart, REFERENCE_TIME* rtStop )
{
        ACMSTREAMHEADER ash;
	MMRESULT mr;
	HRESULT hr = E_FAIL;
	DWORD	dwConvCallFlags;
	DWORD	cb;
	IMediaSample*	pSampOut = NULL;
	BYTE*	pOutBuf;
	LONG	lOutBufLen;

	TRACE("()\n");

	if ( This->pConvBuf == NULL )
		return E_UNEXPECTED;

	dwConvCallFlags = ACM_STREAMCONVERTF_BLOCKALIGN;
	if ( dwConvertFlags & ACM_STREAMCONVERTF_START )
	{
		dwConvCallFlags |= ACM_STREAMCONVERTF_START;
		This->cbConvCached = 0;
	}

	while ( 1 )
	{
		cb = cbSrcLen + This->cbConvCached;
		if ( cb > This->cbConvAllocated )
			cb = This->cbConvAllocated;
		cb -= This->cbConvCached;
		if ( cb > 0 )
		{
			memcpy( This->pConvBuf+This->cbConvCached,
				pbSrc, cb );
			pbSrc += cb;
			cbSrcLen -= cb;
			This->cbConvCached += cb;
		}

		cb = This->cbConvCached / This->cbConvBlockSize * This->cbConvBlockSize;
		if ( cb == 0 )
		{
			if ( dwConvertFlags & ACM_STREAMCONVERTF_END )
			{
				dwConvCallFlags &= ~ACM_STREAMCONVERTF_BLOCKALIGN;
				dwConvCallFlags |= ACM_STREAMCONVERTF_END;
				cb = This->cbConvCached;
			}
			if ( cb == 0 )
			{
				hr = S_OK;
				break;
			}
		}

		ZeroMemory( &ash, sizeof(ash) );
		ash.cbStruct = sizeof(ash);
		ash.pbSrc = This->pConvBuf;
		ash.cbSrcLength = cb;

		hr = IMemAllocator_GetBuffer(
			pImpl->m_pOutPinAllocator,
			&pSampOut, rtStart, rtStop, 0 );
		if ( FAILED(hr) )
			break;
		hr = IMediaSample_SetSyncPoint( pSampOut, TRUE );
		if ( FAILED(hr) )
			break;
		if ( dwConvCallFlags & ACM_STREAMCONVERTF_START )
		{
			hr = IMediaSample_SetDiscontinuity( pSampOut, TRUE );
			if ( FAILED(hr) )
				break;
		}
		hr = IMediaSample_GetPointer( pSampOut, &pOutBuf );
		if ( FAILED(hr) )
			break;
		lOutBufLen = IMediaSample_GetSize( pSampOut );
		if ( lOutBufLen <= 0 )
		{
			hr = E_FAIL;
			break;
		}
		ash.pbDst = pOutBuf;
		ash.cbDstLength = lOutBufLen;

		mr = acmStreamPrepareHeader(
			This->has, &ash, 0 );
		if ( mr == MMSYSERR_NOERROR )
			mr = acmStreamConvert(
				This->has, &ash, dwConvCallFlags );
		if ( mr == MMSYSERR_NOERROR )
			mr = acmStreamUnprepareHeader(
				This->has, &ash, 0 );
		if ( mr != MMSYSERR_NOERROR || ash.cbSrcLengthUsed == 0 )
		{
			hr = E_FAIL;
			break;
		}

		if ( ash.cbDstLengthUsed > 0 )
		{
			hr = IMediaSample_SetActualDataLength( pSampOut, ash.cbDstLengthUsed );
			if ( FAILED(hr) )
				break;

			hr = CPinBaseImpl_SendSample(
				&pImpl->pOutPin->pin,
				pSampOut );
			if ( FAILED(hr) )
				break;
		}

		if ( This->cbConvCached == ash.cbSrcLengthUsed )
		{
			This->cbConvCached = 0;
		}
		else
		{
			This->cbConvCached -= ash.cbSrcLengthUsed;
			memmove( This->pConvBuf,
				 This->pConvBuf + ash.cbSrcLengthUsed,
				 This->cbConvCached );
		}

		IMediaSample_Release( pSampOut ); pSampOut = NULL;
		dwConvCallFlags &= ~ACM_STREAMCONVERTF_START;
	}

	if ( pSampOut != NULL )
		IMediaSample_Release( pSampOut );

	return hr;
}

static HRESULT ACMWrapper_ProcessReceive( CTransformBaseImpl* pImpl, IMediaSample* pSampIn )
{
	CACMWrapperImpl*	This = pImpl->m_pUserData;
	BYTE*	pDataIn = NULL;
	LONG	lDataInLen;
	HRESULT hr;
	DWORD	dwConvFlags = 0;
	REFERENCE_TIME rtStart, rtStop;

	FIXME("(%p)\n",This);

	if ( This == NULL || This->has == (HACMSTREAM)NULL )
		return E_UNEXPECTED;

	hr = IMediaSample_GetPointer( pSampIn, &pDataIn );
	if ( FAILED(hr) )
		return hr;
	lDataInLen = IMediaSample_GetActualDataLength( pSampIn );
	if ( lDataInLen < 0 )
		return E_FAIL;
	if ( IMediaSample_IsDiscontinuity( pSampIn ) == S_OK )
		dwConvFlags |= ACM_STREAMCONVERTF_START;
	hr = IMediaSample_GetTime( pSampIn, &rtStart, &rtStop );
	if ( hr == S_OK )
		return ACMWrapper_Convert(
			pImpl, This, pDataIn, (DWORD)lDataInLen,
			dwConvFlags, &rtStart, &rtStop );
	else
		return ACMWrapper_Convert(
			pImpl, This, pDataIn, (DWORD)lDataInLen,
			dwConvFlags, NULL, NULL );
}

static HRESULT ACMWrapper_EndTransform( CTransformBaseImpl* pImpl )
{
	CACMWrapperImpl*	This = pImpl->m_pUserData;
	HRESULT hr;
	DWORD	dwConvFlags = ACM_STREAMCONVERTF_END;

	TRACE("(%p)\n",This);

	if ( This == NULL )
		return E_UNEXPECTED;

	hr = ACMWrapper_Convert(
		pImpl, This, NULL, 0,
		dwConvFlags, NULL, NULL );

	ACMWrapper_Close( This );
	ACMWrapper_CleanupMTypes( This );
	ACMWrapper_CleanupConvBuf( This );

	return hr;
}

static const TransformBaseHandlers transhandlers =
{
	ACMWrapper_Init,
	ACMWrapper_Cleanup,
	ACMWrapper_CheckMediaType,
	ACMWrapper_GetOutputTypes,
	ACMWrapper_GetAllocProp,
	ACMWrapper_BeginTransform,
	ACMWrapper_ProcessReceive,
	NULL,
	ACMWrapper_EndTransform,
};

HRESULT QUARTZ_CreateACMWrapper(IUnknown* punkOuter,void** ppobj)
{
	return QUARTZ_CreateTransformBase(
		punkOuter,ppobj,
		&CLSID_ACMWrapper,
		ACMWrapper_FilterName,
		NULL, NULL,
		&transhandlers );
}


