/* * Implements ACM Wrapper(CLSID_ACMWrapper). * * FIXME - stub * FIXME - no encoding * * Copyright (C) 2002 Hidenori TAKESHIMA * * 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*)pmtOut, (WAVEFORMATEX*)pmtIn ); if ( FAILED(hr) ) return E_FAIL; return S_OK; } static HRESULT ACMWrapper_Convert( CTransformBaseImpl* pImpl, CACMWrapperImpl* This, BYTE* pbSrc, DWORD cbSrcLen, DWORD dwConvertFlags ) { 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, NULL, NULL, 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; 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; return ACMWrapper_Convert( pImpl, This, pDataIn, (DWORD)lDataInLen, dwConvFlags ); } 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 ); 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 ); }