| /* |
| * ACM Wrapper |
| * |
| * Copyright 2005 Christian Costa |
| * |
| * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
| */ |
| |
| #include "config.h" |
| |
| #include "quartz_private.h" |
| #include "control_private.h" |
| #include "pin.h" |
| |
| #include "uuids.h" |
| #include "mmreg.h" |
| #include "windef.h" |
| #include "winbase.h" |
| #include "dshow.h" |
| #include "strmif.h" |
| #include "vfwmsgs.h" |
| #include "evcode.h" |
| #include "msacm.h" |
| |
| #include <assert.h> |
| |
| #include "wine/unicode.h" |
| #include "wine/debug.h" |
| |
| #include "transform.h" |
| |
| WINE_DEFAULT_DEBUG_CHANNEL(quartz); |
| |
| /* FIXME: Improve buffers management */ |
| #define OUTPUT_BUFFER_SIZE 15000 |
| #define INPUT_BUFFER_SIZE 4096 |
| |
| typedef struct ACMWrapperImpl |
| { |
| TransformFilterImpl tf; |
| HACMSTREAM has; |
| LPWAVEFORMATEX pWfIn; |
| LPWAVEFORMATEX pWfOut; |
| BYTE buffer[INPUT_BUFFER_SIZE]; |
| DWORD max_size; |
| DWORD current_size; |
| BOOL reinit_codec; /* FIXME: Should use sync points instead */ |
| } ACMWrapperImpl; |
| |
| static HRESULT ACMWrapper_ProcessSampleData(TransformFilterImpl* pTransformFilter, LPBYTE data, DWORD size) |
| { |
| ACMWrapperImpl* This = (ACMWrapperImpl*)pTransformFilter; |
| AM_MEDIA_TYPE amt; |
| HRESULT hr; |
| IMediaSample* pSample = NULL; |
| DWORD cbDstStream; |
| LPBYTE pbDstStream; |
| ACMSTREAMHEADER ash; |
| DWORD offset = 0; |
| BOOL stop = FALSE; |
| BOOL unprepare_header = FALSE; |
| MMRESULT res; |
| |
| TRACE("(%p)->(%p,%d)\n", This, data, size); |
| |
| hr = IPin_ConnectionMediaType(This->tf.ppPins[0], &amt); |
| if (FAILED(hr)) { |
| ERR("Unable to retrieve media type\n"); |
| goto error; |
| } |
| |
| while(!stop) |
| { |
| DWORD rem_buf = This->max_size - This->current_size; |
| DWORD rem_smp = size - offset; |
| DWORD copy_size = min(rem_buf, rem_smp); |
| |
| memcpy(This->buffer + This->current_size, data + offset, copy_size); |
| This->current_size += copy_size; |
| offset += copy_size; |
| |
| if (offset == size) |
| stop = TRUE; |
| if (This->current_size < This->max_size) |
| break; |
| |
| hr = OutputPin_GetDeliveryBuffer((OutputPin*)This->tf.ppPins[1], &pSample, NULL, NULL, 0); |
| if (FAILED(hr)) { |
| ERR("Unable to get delivery buffer (%x)\n", hr); |
| goto error; |
| } |
| |
| hr = IMediaSample_SetActualDataLength(pSample, 0); |
| assert(hr == S_OK); |
| |
| hr = IMediaSample_GetPointer(pSample, &pbDstStream); |
| if (FAILED(hr)) { |
| ERR("Unable to get pointer to buffer (%x)\n", hr); |
| goto error; |
| } |
| cbDstStream = IMediaSample_GetSize(pSample); |
| |
| ash.cbStruct = sizeof(ash); |
| ash.fdwStatus = 0; |
| ash.dwUser = 0; |
| ash.pbSrc = This->buffer; |
| ash.cbSrcLength = This->current_size; |
| ash.pbDst = pbDstStream; |
| ash.cbDstLength = cbDstStream; |
| |
| if ((res = acmStreamPrepareHeader(This->has, &ash, 0))) { |
| ERR("Cannot prepare header %d\n", res); |
| goto error; |
| } |
| |
| unprepare_header = TRUE; |
| |
| if ((res = acmStreamConvert(This->has, &ash, This->reinit_codec ? ACM_STREAMCONVERTF_START : 0))) { |
| ERR("Cannot convert data header %d\n", res); |
| goto error; |
| } |
| This->reinit_codec = FALSE; |
| |
| TRACE("used in %u, used out %u\n", ash.cbSrcLengthUsed, ash.cbDstLengthUsed); |
| |
| hr = IMediaSample_SetActualDataLength(pSample, ash.cbDstLengthUsed); |
| assert(hr == S_OK); |
| |
| if (ash.cbSrcLengthUsed < ash.cbSrcLength) { |
| This->current_size = ash.cbSrcLength - ash.cbSrcLengthUsed; |
| memmove(This->buffer, This->buffer + ash.cbSrcLengthUsed, This->current_size); |
| } |
| else |
| This->current_size = 0; |
| |
| hr = OutputPin_SendSample((OutputPin*)This->tf.ppPins[1], pSample); |
| if (hr != S_OK && hr != VFW_E_NOT_CONNECTED) { |
| ERR("Error sending sample (%x)\n", hr); |
| goto error; |
| } |
| |
| error: |
| if (unprepare_header && (res = acmStreamUnprepareHeader(This->has, &ash, 0))) |
| ERR("Cannot unprepare header %d\n", res); |
| |
| if (pSample) |
| IMediaSample_Release(pSample); |
| } |
| |
| return hr; |
| } |
| |
| static HRESULT ACMWrapper_ConnectInput(TransformFilterImpl* pTransformFilter, const AM_MEDIA_TYPE * pmt) |
| { |
| ACMWrapperImpl* This = (ACMWrapperImpl*)pTransformFilter; |
| MMRESULT res; |
| |
| TRACE("(%p)->(%p)\n", This, pmt); |
| |
| /* Check root (GUID w/o FOURCC) */ |
| if ((IsEqualIID(&pmt->majortype, &MEDIATYPE_Audio)) && |
| (!memcmp(((const char *)&pmt->subtype)+4, ((const char *)&MEDIATYPE_Audio)+4, sizeof(GUID)-4)) && |
| (IsEqualIID(&pmt->formattype, &FORMAT_WaveFormatEx))) |
| { |
| HACMSTREAM drv; |
| AM_MEDIA_TYPE* outpmt = &((OutputPin*)This->tf.ppPins[1])->pin.mtCurrent; |
| This->pWfIn = (LPWAVEFORMATEX)pmt->pbFormat; |
| |
| /* HACK */ |
| /* TRACE("ALIGN = %d\n", pACMWrapper->pWfIn->nBlockAlign); */ |
| /* pACMWrapper->pWfIn->nBlockAlign = 1; */ |
| |
| /* Set output audio data to PCM */ |
| CopyMediaType(outpmt, pmt); |
| outpmt->subtype.Data1 = WAVE_FORMAT_PCM; |
| This->pWfOut = (WAVEFORMATEX*)outpmt->pbFormat; |
| This->pWfOut->wFormatTag = WAVE_FORMAT_PCM; |
| This->pWfOut->wBitsPerSample = 16; |
| This->pWfOut->nBlockAlign = 4; |
| This->pWfOut->cbSize = 0; |
| This->pWfOut->nAvgBytesPerSec = This->pWfOut->nChannels * This->pWfOut->nSamplesPerSec |
| * (This->pWfOut->wBitsPerSample/8); |
| |
| if (!(res = acmStreamOpen(&drv, NULL, This->pWfIn, This->pWfOut, NULL, 0, 0, 0))) |
| { |
| This->has = drv; |
| |
| if ((res = acmStreamSize(drv, OUTPUT_BUFFER_SIZE, &This->max_size, ACM_STREAMSIZEF_DESTINATION))) { |
| ERR("Cannot retrieve input buffer size error %d!\n", res); |
| This->max_size = INPUT_BUFFER_SIZE; |
| } |
| |
| TRACE("input buffer size %d\n", This->max_size); |
| |
| /* Update buffer size of media samples in output */ |
| ((OutputPin*)This->tf.ppPins[1])->allocProps.cbBuffer = OUTPUT_BUFFER_SIZE; |
| |
| TRACE("Connection accepted\n"); |
| return S_OK; |
| } |
| else |
| FIXME("acmStreamOpen returned %d\n", res); |
| FreeMediaType(outpmt); |
| TRACE("Unable to find a suitable ACM decompressor\n"); |
| } |
| |
| TRACE("Connection refused\n"); |
| return S_FALSE; |
| } |
| |
| static HRESULT ACMWrapper_Cleanup(TransformFilterImpl* pTransformFilter) |
| { |
| ACMWrapperImpl* This = (ACMWrapperImpl*)pTransformFilter; |
| |
| TRACE("(%p)->()\n", This); |
| |
| if (This->has) |
| acmStreamClose(This->has, 0); |
| |
| This->has = 0; |
| |
| return S_OK; |
| } |
| |
| static const TransformFuncsTable ACMWrapper_FuncsTable = { |
| NULL, |
| ACMWrapper_ProcessSampleData, |
| NULL, |
| ACMWrapper_ConnectInput, |
| ACMWrapper_Cleanup |
| }; |
| |
| HRESULT ACMWrapper_create(IUnknown * pUnkOuter, LPVOID * ppv) |
| { |
| HRESULT hr; |
| ACMWrapperImpl* This; |
| |
| TRACE("(%p, %p)\n", pUnkOuter, ppv); |
| |
| *ppv = NULL; |
| |
| if (pUnkOuter) |
| return CLASS_E_NOAGGREGATION; |
| |
| /* Note: This memory is managed by the transform filter once created */ |
| This = CoTaskMemAlloc(sizeof(ACMWrapperImpl)); |
| |
| This->has = 0; |
| This->reinit_codec = TRUE; |
| |
| hr = TransformFilter_Create(&(This->tf), &CLSID_ACMWrapper, &ACMWrapper_FuncsTable); |
| |
| if (FAILED(hr)) |
| return hr; |
| |
| *ppv = (LPVOID)This; |
| |
| return hr; |
| } |