acmwrapper.c 13.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/*
 * 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
18
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
 */

#include "config.h"

#include "quartz_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 "msacm.h"

#include <assert.h>

#include "wine/unicode.h"
#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(quartz);

typedef struct ACMWrapperImpl
{
44 45 46
    TransformFilter tf;
    IUnknown *seekthru_unk;

47 48 49
    HACMSTREAM has;
    LPWAVEFORMATEX pWfIn;
    LPWAVEFORMATEX pWfOut;
50 51 52

    LONGLONG lasttime_real;
    LONGLONG lasttime_sent;
53 54
} ACMWrapperImpl;

55 56
static const IBaseFilterVtbl ACMWrapper_Vtbl;

57 58 59 60 61 62 63 64 65 66
static inline ACMWrapperImpl *impl_from_TransformFilter( TransformFilter *iface )
{
    return CONTAINING_RECORD(iface, ACMWrapperImpl, tf.filter);
}

static inline ACMWrapperImpl *impl_from_IBaseFilter( IBaseFilter *iface )
{
    return CONTAINING_RECORD(iface, ACMWrapperImpl, tf.filter.IBaseFilter_iface);
}

67
static HRESULT WINAPI ACMWrapper_Receive(TransformFilter *tf, IMediaSample *pSample)
68
{
69
    ACMWrapperImpl* This = impl_from_TransformFilter(tf);
70
    AM_MEDIA_TYPE amt;
71
    IMediaSample* pOutSample = NULL;
72
    DWORD cbDstStream, cbSrcStream;
73
    LPBYTE pbDstStream;
74
    LPBYTE pbSrcStream = NULL;
75
    ACMSTREAMHEADER ash;
76
    BOOL unprepare_header = FALSE, preroll;
77
    MMRESULT res;
78
    HRESULT hr;
79
    LONGLONG tStart = -1, tStop = -1, tMed;
80
    LONGLONG mtStart = -1, mtStop = -1, mtMed;
81

82
    EnterCriticalSection(&This->tf.csReceive);
83 84 85 86
    hr = IMediaSample_GetPointer(pSample, &pbSrcStream);
    if (FAILED(hr))
    {
        ERR("Cannot get pointer to sample data (%x)\n", hr);
87
        LeaveCriticalSection(&This->tf.csReceive);
88
        return hr;
89
    }
90

91
    preroll = (IMediaSample_IsPreroll(pSample) == S_OK);
92

93
    IMediaSample_GetTime(pSample, &tStart, &tStop);
94 95
    if (IMediaSample_GetMediaTime(pSample, &mtStart, &mtStop) != S_OK)
        mtStart = mtStop = -1;
96
    cbSrcStream = IMediaSample_GetActualDataLength(pSample);
97

98 99
    /* Prevent discontinuities when codecs 'absorb' data but not give anything back in return */
    if (IMediaSample_IsDiscontinuity(pSample) == S_OK)
100
    {
101 102
        This->lasttime_real = tStart;
        This->lasttime_sent = tStart;
103
    }
104 105 106 107 108
    else if (This->lasttime_real == tStart)
        tStart = This->lasttime_sent;
    else
        WARN("Discontinuity\n");

109
    tMed = tStart;
110
    mtMed = mtStart;
111

112
    TRACE("Sample data ptr = %p, size = %d\n", pbSrcStream, cbSrcStream);
113 114

    hr = IPin_ConnectionMediaType(This->tf.ppPins[0], &amt);
115 116 117
    if (FAILED(hr))
    {
        ERR("Unable to retrieve media type\n");
118
        LeaveCriticalSection(&This->tf.csReceive);
119
        return hr;
120 121
    }

122 123 124 125
    ash.pbSrc = pbSrcStream;
    ash.cbSrcLength = cbSrcStream;

    while(hr == S_OK && ash.cbSrcLength)
126
    {
127
        hr = BaseOutputPinImpl_GetDeliveryBuffer((BaseOutputPin*)This->tf.ppPins[1], &pOutSample, NULL, NULL, 0);
128 129 130
        if (FAILED(hr))
        {
            ERR("Unable to get delivery buffer (%x)\n", hr);
131
            LeaveCriticalSection(&This->tf.csReceive);
132 133 134
            return hr;
        }
        IMediaSample_SetPreroll(pOutSample, preroll);
135

136
	hr = IMediaSample_SetActualDataLength(pOutSample, 0);
137 138
	assert(hr == S_OK);

139
	hr = IMediaSample_GetPointer(pOutSample, &pbDstStream);
140
	if (FAILED(hr)) {
141
	    ERR("Unable to get pointer to buffer (%x)\n", hr);
142 143
	    goto error;
	}
144
	cbDstStream = IMediaSample_GetSize(pOutSample);
145 146 147 148 149 150 151 152 153 154 155 156 157

	ash.cbStruct = sizeof(ash);
	ash.fdwStatus = 0;
	ash.dwUser = 0;
	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;

158
        if (IMediaSample_IsDiscontinuity(pSample) == S_OK)
159
        {
160
            res = acmStreamConvert(This->has, &ash, ACM_STREAMCONVERTF_START);
161
            IMediaSample_SetDiscontinuity(pOutSample, TRUE);
162 163 164
            /* One sample could be converted to multiple packets */
            IMediaSample_SetDiscontinuity(pSample, FALSE);
        }
165
        else
166
        {
167
            res = acmStreamConvert(This->has, &ash, 0);
168 169
            IMediaSample_SetDiscontinuity(pOutSample, FALSE);
        }
170 171 172

        if (res)
        {
173 174 175 176
            if(res != MMSYSERR_MOREDATA)
                ERR("Cannot convert data header %d\n", res);
            goto error;
        }
177

178
        TRACE("used in %u/%u, used out %u/%u\n", ash.cbSrcLengthUsed, ash.cbSrcLength, ash.cbDstLengthUsed, ash.cbDstLength);
179

180 181
        hr = IMediaSample_SetActualDataLength(pOutSample, ash.cbDstLengthUsed);
        assert(hr == S_OK);
182

183
        /* Bug in acm codecs? It apparently uses the input, but doesn't necessarily output immediately */
184 185
        if (!ash.cbSrcLengthUsed)
        {
186
            WARN("Sample was skipped? Outputted: %u\n", ash.cbDstLengthUsed);
187 188 189 190
            ash.cbSrcLength = 0;
            goto error;
        }

191
        TRACE("Sample start time: %u.%03u\n", (DWORD)(tStart/10000000), (DWORD)((tStart/10000)%1000));
192 193 194
        if (ash.cbSrcLengthUsed == cbSrcStream)
        {
            IMediaSample_SetTime(pOutSample, &tStart, &tStop);
195
            tStart = tMed = tStop;
196 197 198 199 200 201 202 203 204 205 206 207 208
        }
        else if (tStop != tStart)
        {
            tMed = tStop - tStart;
            tMed = tStart + tMed * ash.cbSrcLengthUsed / cbSrcStream;
            IMediaSample_SetTime(pOutSample, &tStart, &tMed);
            tStart = tMed;
        }
        else
        {
            ERR("No valid timestamp found\n");
            IMediaSample_SetTime(pOutSample, NULL, NULL);
        }
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223

        if (mtStart < 0) {
            IMediaSample_SetMediaTime(pOutSample, NULL, NULL);
        } else if (ash.cbSrcLengthUsed == cbSrcStream) {
            IMediaSample_SetMediaTime(pOutSample, &mtStart, &mtStop);
            mtStart = mtMed = mtStop;
        } else if (mtStop >= mtStart) {
            mtMed = mtStop - mtStart;
            mtMed = mtStart + mtMed * ash.cbSrcLengthUsed / cbSrcStream;
            IMediaSample_SetMediaTime(pOutSample, &mtStart, &mtMed);
            mtStart = mtMed;
        } else {
            IMediaSample_SetMediaTime(pOutSample, NULL, NULL);
        }

224
        TRACE("Sample stop time: %u.%03u\n", (DWORD)(tStart/10000000), (DWORD)((tStart/10000)%1000));
225

226
        LeaveCriticalSection(&This->tf.csReceive);
227
        hr = BaseOutputPinImpl_Deliver((BaseOutputPin*)This->tf.ppPins[1], pOutSample);
228
        EnterCriticalSection(&This->tf.csReceive);
229

230 231 232 233
        if (hr != S_OK && hr != VFW_E_NOT_CONNECTED) {
            if (FAILED(hr))
                ERR("Error sending sample (%x)\n", hr);
            goto error;
234 235 236
        }

error:
237 238 239
        if (unprepare_header && (res = acmStreamUnprepareHeader(This->has, &ash, 0)))
            ERR("Cannot unprepare header %d\n", res);
        unprepare_header = FALSE;
240 241
        ash.pbSrc += ash.cbSrcLengthUsed;
        ash.cbSrcLength -= ash.cbSrcLengthUsed;
242

243
        IMediaSample_Release(pOutSample);
244
        pOutSample = NULL;
245

246 247
    }

248 249 250
    This->lasttime_real = tStop;
    This->lasttime_sent = tMed;

251
    LeaveCriticalSection(&This->tf.csReceive);
252 253 254
    return hr;
}

255
static HRESULT WINAPI ACMWrapper_SetMediaType(TransformFilter *tf, PIN_DIRECTION dir, const AM_MEDIA_TYPE * pmt)
256
{
257
    ACMWrapperImpl* This = impl_from_TransformFilter(tf);
258 259
    MMRESULT res;

260 261 262 263
    TRACE("(%p)->(%i %p)\n", This, dir, pmt);

    if (dir != PINDIR_INPUT)
        return S_OK;
264

265
    /* Check root (GUID w/o FOURCC) */
266
    if ((IsEqualIID(&pmt->majortype, &MEDIATYPE_Audio)) &&
267
        (!memcmp(((const char *)&pmt->subtype)+4, ((const char *)&MEDIATYPE_Audio)+4, sizeof(GUID)-4)) &&
268 269 270
        (IsEqualIID(&pmt->formattype, &FORMAT_WaveFormatEx)))
    {
        HACMSTREAM drv;
271
        WAVEFORMATEX *wfx = (WAVEFORMATEX*)pmt->pbFormat;
272
        AM_MEDIA_TYPE* outpmt = &This->tf.pmt;
273 274 275

        if (!wfx || wfx->wFormatTag == WAVE_FORMAT_PCM || wfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
            return VFW_E_TYPE_NOT_ACCEPTED;
276 277
        FreeMediaType(outpmt);

278 279 280 281 282 283 284 285 286 287 288 289
        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;
290
	This->pWfOut->nBlockAlign = This->pWfOut->wBitsPerSample * This->pWfOut->nChannels / 8;
291 292 293 294 295 296 297 298 299 300 301 302 303
	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;

            TRACE("Connection accepted\n");
            return S_OK;
        }
	else
	    FIXME("acmStreamOpen returned %d\n", res);
304
        FreeMediaType(outpmt);
305 306 307 308
        TRACE("Unable to find a suitable ACM decompressor\n");
    }

    TRACE("Connection refused\n");
309
    return VFW_E_TYPE_NOT_ACCEPTED;
310 311
}

312
static HRESULT WINAPI ACMWrapper_CompleteConnect(TransformFilter *tf, PIN_DIRECTION dir, IPin *pin)
313
{
314
    ACMWrapperImpl* This = impl_from_TransformFilter(tf);
315 316
    MMRESULT res;
    HACMSTREAM drv;
317

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

320 321
    if (dir != PINDIR_INPUT)
        return S_OK;
322

323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
    if (!(res = acmStreamOpen(&drv, NULL, This->pWfIn, This->pWfOut, NULL, 0, 0, 0)))
    {
        This->has = drv;

        TRACE("Connection accepted\n");
        return S_OK;
    }

    FIXME("acmStreamOpen returned %d\n", res);
    TRACE("Unable to find a suitable ACM decompressor\n");
    return VFW_E_TYPE_NOT_ACCEPTED;
}

static HRESULT WINAPI ACMWrapper_BreakConnect(TransformFilter *tf, PIN_DIRECTION dir)
{
338
    ACMWrapperImpl *This = impl_from_TransformFilter(tf);
339 340 341 342 343 344 345 346 347 348 349

    TRACE("(%p)->(%i)\n", This,dir);

    if (dir == PINDIR_INPUT)
    {
        if (This->has)
            acmStreamClose(This->has, 0);

        This->has = 0;
        This->lasttime_real = This->lasttime_sent = -1;
    }
350

351 352 353
    return S_OK;
}

354 355
static HRESULT WINAPI ACMWrapper_DecideBufferSize(TransformFilter *tf, IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *ppropInputRequest)
{
356
    ACMWrapperImpl *pACM = impl_from_TransformFilter(tf);
357 358 359 360 361 362 363 364 365 366 367 368 369 370
    ALLOCATOR_PROPERTIES actual;

    if (!ppropInputRequest->cbAlign)
        ppropInputRequest->cbAlign = 1;

    if (ppropInputRequest->cbBuffer < pACM->pWfOut->nAvgBytesPerSec / 2)
            ppropInputRequest->cbBuffer = pACM->pWfOut->nAvgBytesPerSec / 2;

    if (!ppropInputRequest->cBuffers)
        ppropInputRequest->cBuffers = 1;

    return IMemAllocator_SetProperties(pAlloc, ppropInputRequest, &actual);
}

371
static const TransformFilterFuncTable ACMWrapper_FuncsTable = {
372
    ACMWrapper_DecideBufferSize,
373 374 375
    NULL,
    ACMWrapper_Receive,
    NULL,
Christian Costa's avatar
Christian Costa committed
376
    NULL,
377 378 379
    ACMWrapper_SetMediaType,
    ACMWrapper_CompleteConnect,
    ACMWrapper_BreakConnect,
Christian Costa's avatar
Christian Costa committed
380
    NULL,
381
    NULL,
382 383
    NULL,
    NULL
Christian Costa's avatar
Christian Costa committed
384 385
};

386 387 388 389 390 391 392 393 394 395 396 397
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;

398
    hr = TransformFilter_Construct(&ACMWrapper_Vtbl, sizeof(ACMWrapperImpl), &CLSID_ACMWrapper, &ACMWrapper_FuncsTable, (IBaseFilter**)&This);
399 400 401

    if (FAILED(hr))
        return hr;
402 403 404 405 406
    else
    {
        ISeekingPassThru *passthru;
        hr = CoCreateInstance(&CLSID_SeekingPassThru, (IUnknown*)This, CLSCTX_INPROC_SERVER, &IID_IUnknown, (void**)&This->seekthru_unk);
        IUnknown_QueryInterface(This->seekthru_unk, &IID_ISeekingPassThru, (void**)&passthru);
407
        ISeekingPassThru_Init(passthru, FALSE, This->tf.ppPins[0]);
408 409
        ISeekingPassThru_Release(passthru);
    }
410

411
    *ppv = This;
412
    This->lasttime_real = This->lasttime_sent = -1;
413 414 415

    return hr;
}
416

417
static HRESULT WINAPI ACMWrapper_QueryInterface(IBaseFilter * iface, REFIID riid, LPVOID * ppv)
418 419
{
    HRESULT hr;
420
    ACMWrapperImpl *This = impl_from_IBaseFilter(iface);
421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449
    TRACE("(%p/%p)->(%s, %p)\n", This, iface, qzdebugstr_guid(riid), ppv);

    if (IsEqualIID(riid, &IID_IMediaSeeking))
        return IUnknown_QueryInterface(This->seekthru_unk, riid, ppv);

    hr = TransformFilterImpl_QueryInterface(iface, riid, ppv);

    return hr;
}


static const IBaseFilterVtbl ACMWrapper_Vtbl =
{
    ACMWrapper_QueryInterface,
    BaseFilterImpl_AddRef,
    TransformFilterImpl_Release,
    BaseFilterImpl_GetClassID,
    TransformFilterImpl_Stop,
    TransformFilterImpl_Pause,
    TransformFilterImpl_Run,
    BaseFilterImpl_GetState,
    BaseFilterImpl_SetSyncSource,
    BaseFilterImpl_GetSyncSource,
    BaseFilterImpl_EnumPins,
    TransformFilterImpl_FindPin,
    BaseFilterImpl_QueryFilterInfo,
    BaseFilterImpl_JoinFilterGraph,
    BaseFilterImpl_QueryVendorInfo
};