acmwrapper.c 13 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 57
static const IBaseFilterVtbl ACMWrapper_Vtbl;

static HRESULT WINAPI ACMWrapper_Receive(TransformFilter *tf, IMediaSample *pSample)
58
{
59
    ACMWrapperImpl* This = (ACMWrapperImpl*)tf;
60
    AM_MEDIA_TYPE amt;
61
    IMediaSample* pOutSample = NULL;
62
    DWORD cbDstStream, cbSrcStream;
63
    LPBYTE pbDstStream;
64
    LPBYTE pbSrcStream = NULL;
65
    ACMSTREAMHEADER ash;
66
    BOOL unprepare_header = FALSE, preroll;
67
    MMRESULT res;
68
    HRESULT hr;
69
    LONGLONG tStart = -1, tStop = -1, tMed;
70
    LONGLONG mtStart = -1, mtStop = -1, mtMed;
71

72
    EnterCriticalSection(&This->tf.csReceive);
73 74 75 76
    hr = IMediaSample_GetPointer(pSample, &pbSrcStream);
    if (FAILED(hr))
    {
        ERR("Cannot get pointer to sample data (%x)\n", hr);
77
        LeaveCriticalSection(&This->tf.csReceive);
78
        return hr;
79
    }
80

81
    preroll = (IMediaSample_IsPreroll(pSample) == S_OK);
82

83
    IMediaSample_GetTime(pSample, &tStart, &tStop);
84 85
    if (IMediaSample_GetMediaTime(pSample, &mtStart, &mtStop) != S_OK)
        mtStart = mtStop = -1;
86
    cbSrcStream = IMediaSample_GetActualDataLength(pSample);
87

88 89
    /* Prevent discontinuities when codecs 'absorb' data but not give anything back in return */
    if (IMediaSample_IsDiscontinuity(pSample) == S_OK)
90
    {
91 92
        This->lasttime_real = tStart;
        This->lasttime_sent = tStart;
93
    }
94 95 96 97 98
    else if (This->lasttime_real == tStart)
        tStart = This->lasttime_sent;
    else
        WARN("Discontinuity\n");

99
    tMed = tStart;
100
    mtMed = mtStart;
101

102
    TRACE("Sample data ptr = %p, size = %d\n", pbSrcStream, cbSrcStream);
103 104

    hr = IPin_ConnectionMediaType(This->tf.ppPins[0], &amt);
105 106 107
    if (FAILED(hr))
    {
        ERR("Unable to retrieve media type\n");
108
        LeaveCriticalSection(&This->tf.csReceive);
109
        return hr;
110 111
    }

112 113 114 115
    ash.pbSrc = pbSrcStream;
    ash.cbSrcLength = cbSrcStream;

    while(hr == S_OK && ash.cbSrcLength)
116
    {
117
        hr = BaseOutputPinImpl_GetDeliveryBuffer((BaseOutputPin*)This->tf.ppPins[1], &pOutSample, NULL, NULL, 0);
118 119 120
        if (FAILED(hr))
        {
            ERR("Unable to get delivery buffer (%x)\n", hr);
121
            LeaveCriticalSection(&This->tf.csReceive);
122 123 124
            return hr;
        }
        IMediaSample_SetPreroll(pOutSample, preroll);
125

126
	hr = IMediaSample_SetActualDataLength(pOutSample, 0);
127 128
	assert(hr == S_OK);

129
	hr = IMediaSample_GetPointer(pOutSample, &pbDstStream);
130
	if (FAILED(hr)) {
131
	    ERR("Unable to get pointer to buffer (%x)\n", hr);
132 133
	    goto error;
	}
134
	cbDstStream = IMediaSample_GetSize(pOutSample);
135 136 137 138 139 140 141 142 143 144 145 146 147

	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;

148
        if (IMediaSample_IsDiscontinuity(pSample) == S_OK)
149
        {
150
            res = acmStreamConvert(This->has, &ash, ACM_STREAMCONVERTF_START);
151
            IMediaSample_SetDiscontinuity(pOutSample, TRUE);
152 153 154
            /* One sample could be converted to multiple packets */
            IMediaSample_SetDiscontinuity(pSample, FALSE);
        }
155
        else
156
        {
157
            res = acmStreamConvert(This->has, &ash, 0);
158 159
            IMediaSample_SetDiscontinuity(pOutSample, FALSE);
        }
160 161 162

        if (res)
        {
163 164 165 166
            if(res != MMSYSERR_MOREDATA)
                ERR("Cannot convert data header %d\n", res);
            goto error;
        }
167

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

170 171
        hr = IMediaSample_SetActualDataLength(pOutSample, ash.cbDstLengthUsed);
        assert(hr == S_OK);
172

173
        /* Bug in acm codecs? It apparantly uses the input, but doesn't necessarily output immediately kl*/
174 175
        if (!ash.cbSrcLengthUsed)
        {
176
            WARN("Sample was skipped? Outputted: %u\n", ash.cbDstLengthUsed);
177 178 179 180
            ash.cbSrcLength = 0;
            goto error;
        }

181
        TRACE("Sample start time: %u.%03u\n", (DWORD)(tStart/10000000), (DWORD)((tStart/10000)%1000));
182 183 184
        if (ash.cbSrcLengthUsed == cbSrcStream)
        {
            IMediaSample_SetTime(pOutSample, &tStart, &tStop);
185
            tStart = tMed = tStop;
186 187 188 189 190 191 192 193 194 195 196 197 198
        }
        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);
        }
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213

        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);
        }

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

216
        LeaveCriticalSection(&This->tf.csReceive);
217
        hr = BaseOutputPinImpl_Deliver((BaseOutputPin*)This->tf.ppPins[1], pOutSample);
218
        EnterCriticalSection(&This->tf.csReceive);
219

220 221 222 223
        if (hr != S_OK && hr != VFW_E_NOT_CONNECTED) {
            if (FAILED(hr))
                ERR("Error sending sample (%x)\n", hr);
            goto error;
224 225 226
        }

error:
227 228 229
        if (unprepare_header && (res = acmStreamUnprepareHeader(This->has, &ash, 0)))
            ERR("Cannot unprepare header %d\n", res);
        unprepare_header = FALSE;
230 231
        ash.pbSrc += ash.cbSrcLengthUsed;
        ash.cbSrcLength -= ash.cbSrcLengthUsed;
232

233
        IMediaSample_Release(pOutSample);
234
        pOutSample = NULL;
235

236 237
    }

238 239 240
    This->lasttime_real = tStop;
    This->lasttime_sent = tMed;

241
    LeaveCriticalSection(&This->tf.csReceive);
242 243 244
    return hr;
}

245
static HRESULT WINAPI ACMWrapper_SetMediaType(TransformFilter *tf, PIN_DIRECTION dir, const AM_MEDIA_TYPE * pmt)
246
{
247
    ACMWrapperImpl* This = (ACMWrapperImpl *)tf;
248 249
    MMRESULT res;

250 251 252 253
    TRACE("(%p)->(%i %p)\n", This, dir, pmt);

    if (dir != PINDIR_INPUT)
        return S_OK;
254

255
    /* Check root (GUID w/o FOURCC) */
256
    if ((IsEqualIID(&pmt->majortype, &MEDIATYPE_Audio)) &&
257
        (!memcmp(((const char *)&pmt->subtype)+4, ((const char *)&MEDIATYPE_Audio)+4, sizeof(GUID)-4)) &&
258 259 260
        (IsEqualIID(&pmt->formattype, &FORMAT_WaveFormatEx)))
    {
        HACMSTREAM drv;
261
        WAVEFORMATEX *wfx = (WAVEFORMATEX*)pmt->pbFormat;
262
        AM_MEDIA_TYPE* outpmt = &This->tf.pmt;
263 264 265

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

268 269 270 271 272 273 274 275 276 277 278 279
        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;
280
	This->pWfOut->nBlockAlign = This->pWfOut->wBitsPerSample * This->pWfOut->nChannels / 8;
281 282 283 284 285 286 287 288 289 290 291 292 293
	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);
294
        FreeMediaType(outpmt);
295 296 297 298
        TRACE("Unable to find a suitable ACM decompressor\n");
    }

    TRACE("Connection refused\n");
299
    return VFW_E_TYPE_NOT_ACCEPTED;
300 301
}

302
static HRESULT WINAPI ACMWrapper_CompleteConnect(TransformFilter *tf, PIN_DIRECTION dir, IPin *pin)
303
{
304 305 306
    ACMWrapperImpl* This = (ACMWrapperImpl *)tf;
    MMRESULT res;
    HACMSTREAM drv;
307

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

310 311
    if (dir != PINDIR_INPUT)
        return S_OK;
312

313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
    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)
{
    ACMWrapperImpl *This = (ACMWrapperImpl *)tf;

    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;
    }
340

341 342 343
    return S_OK;
}

344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360
static HRESULT WINAPI ACMWrapper_DecideBufferSize(TransformFilter *tf, IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *ppropInputRequest)
{
    ACMWrapperImpl *pACM = (ACMWrapperImpl*)tf;
    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);
}

361
static const TransformFilterFuncTable ACMWrapper_FuncsTable = {
362
    ACMWrapper_DecideBufferSize,
363 364 365
    NULL,
    ACMWrapper_Receive,
    NULL,
Christian Costa's avatar
Christian Costa committed
366
    NULL,
367 368 369
    ACMWrapper_SetMediaType,
    ACMWrapper_CompleteConnect,
    ACMWrapper_BreakConnect,
Christian Costa's avatar
Christian Costa committed
370
    NULL,
371
    NULL,
372 373
    NULL,
    NULL
Christian Costa's avatar
Christian Costa committed
374 375
};

376 377 378 379 380 381 382 383 384 385 386 387
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;

388
    hr = TransformFilter_Construct(&ACMWrapper_Vtbl, sizeof(ACMWrapperImpl), &CLSID_ACMWrapper, &ACMWrapper_FuncsTable, (IBaseFilter**)&This);
389 390 391

    if (FAILED(hr))
        return hr;
392 393 394 395 396 397 398 399
    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);
        ISeekingPassThru_Init(passthru, FALSE, (IPin*)This->tf.ppPins[0]);
        ISeekingPassThru_Release(passthru);
    }
400

401
    *ppv = This;
402
    This->lasttime_real = This->lasttime_sent = -1;
403 404 405

    return hr;
}
406

407
static HRESULT WINAPI ACMWrapper_QueryInterface(IBaseFilter * iface, REFIID riid, LPVOID * ppv)
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439
{
    HRESULT hr;
    ACMWrapperImpl *This = (ACMWrapperImpl *)iface;
    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
};