acmwrapper.c 9.93 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
 */

#include "quartz_private.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/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(quartz);

typedef struct ACMWrapperImpl
{
40 41
    TransformFilter tf;

42 43
    HACMSTREAM has;
    LPWAVEFORMATEX pWfOut;
44 45 46

    LONGLONG lasttime_real;
    LONGLONG lasttime_sent;
47 48
} ACMWrapperImpl;

49 50
static inline ACMWrapperImpl *impl_from_TransformFilter( TransformFilter *iface )
{
51
    return CONTAINING_RECORD(iface, ACMWrapperImpl, tf);
52 53
}

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

68 69 70 71
    hr = IMediaSample_GetPointer(pSample, &pbSrcStream);
    if (FAILED(hr))
    {
        ERR("Cannot get pointer to sample data (%x)\n", hr);
72
        return hr;
73
    }
74

75
    preroll = (IMediaSample_IsPreroll(pSample) == S_OK);
76

77
    IMediaSample_GetTime(pSample, &tStart, &tStop);
78 79
    if (IMediaSample_GetMediaTime(pSample, &mtStart, &mtStop) != S_OK)
        mtStart = mtStop = -1;
80
    cbSrcStream = IMediaSample_GetActualDataLength(pSample);
81

82 83
    /* Prevent discontinuities when codecs 'absorb' data but not give anything back in return */
    if (IMediaSample_IsDiscontinuity(pSample) == S_OK)
84
    {
85 86
        This->lasttime_real = tStart;
        This->lasttime_sent = tStart;
87
    }
88 89 90 91 92
    else if (This->lasttime_real == tStart)
        tStart = This->lasttime_sent;
    else
        WARN("Discontinuity\n");

93
    tMed = tStart;
94
    mtMed = mtStart;
95

96
    TRACE("Sample data ptr = %p, size = %d\n", pbSrcStream, cbSrcStream);
97

98 99 100 101
    ash.pbSrc = pbSrcStream;
    ash.cbSrcLength = cbSrcStream;

    while(hr == S_OK && ash.cbSrcLength)
102
    {
103
        hr = BaseOutputPinImpl_GetDeliveryBuffer(&This->tf.source, &pOutSample, NULL, NULL, 0);
104 105 106 107 108 109
        if (FAILED(hr))
        {
            ERR("Unable to get delivery buffer (%x)\n", hr);
            return hr;
        }
        IMediaSample_SetPreroll(pOutSample, preroll);
110

111
	hr = IMediaSample_SetActualDataLength(pOutSample, 0);
112 113
	assert(hr == S_OK);

114
	hr = IMediaSample_GetPointer(pOutSample, &pbDstStream);
115
	if (FAILED(hr)) {
116
	    ERR("Unable to get pointer to buffer (%x)\n", hr);
117 118
	    goto error;
	}
119
	cbDstStream = IMediaSample_GetSize(pOutSample);
120 121 122 123 124 125 126 127 128 129 130 131 132

	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;

133
        if (IMediaSample_IsDiscontinuity(pSample) == S_OK)
134
        {
135
            res = acmStreamConvert(This->has, &ash, ACM_STREAMCONVERTF_START);
136
            IMediaSample_SetDiscontinuity(pOutSample, TRUE);
137 138 139
            /* One sample could be converted to multiple packets */
            IMediaSample_SetDiscontinuity(pSample, FALSE);
        }
140
        else
141
        {
142
            res = acmStreamConvert(This->has, &ash, 0);
143 144
            IMediaSample_SetDiscontinuity(pOutSample, FALSE);
        }
145 146 147

        if (res)
        {
148 149 150 151
            if(res != MMSYSERR_MOREDATA)
                ERR("Cannot convert data header %d\n", res);
            goto error;
        }
152

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

155 156
        hr = IMediaSample_SetActualDataLength(pOutSample, ash.cbDstLengthUsed);
        assert(hr == S_OK);
157

158
        /* Bug in acm codecs? It apparently uses the input, but doesn't necessarily output immediately */
159 160
        if (!ash.cbSrcLengthUsed)
        {
161
            WARN("Sample was skipped? Outputted: %u\n", ash.cbDstLengthUsed);
162 163 164 165
            ash.cbSrcLength = 0;
            goto error;
        }

166
        TRACE("Sample start time: %u.%03u\n", (DWORD)(tStart/10000000), (DWORD)((tStart/10000)%1000));
167 168 169
        if (ash.cbSrcLengthUsed == cbSrcStream)
        {
            IMediaSample_SetTime(pOutSample, &tStart, &tStop);
170
            tStart = tMed = tStop;
171 172 173 174 175 176 177 178 179 180 181 182 183
        }
        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);
        }
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198

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

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

201
        hr = IMemInputPin_Receive(This->tf.source.pMemInputPin, pOutSample);
202 203 204 205
        if (hr != S_OK && hr != VFW_E_NOT_CONNECTED) {
            if (FAILED(hr))
                ERR("Error sending sample (%x)\n", hr);
            goto error;
206 207 208
        }

error:
209 210 211
        if (unprepare_header && (res = acmStreamUnprepareHeader(This->has, &ash, 0)))
            ERR("Cannot unprepare header %d\n", res);
        unprepare_header = FALSE;
212 213
        ash.pbSrc += ash.cbSrcLengthUsed;
        ash.cbSrcLength -= ash.cbSrcLengthUsed;
214

215
        IMediaSample_Release(pOutSample);
216
        pOutSample = NULL;
217

218 219
    }

220 221 222
    This->lasttime_real = tStop;
    This->lasttime_sent = tMed;

223 224 225
    return hr;
}

226
static BOOL is_audio_subtype(const GUID *guid)
227
{
228
    return !memcmp(&guid->Data2, &MEDIATYPE_Audio.Data2, sizeof(GUID) - sizeof(int));
229 230
}

231
static HRESULT acm_wrapper_connect_sink(TransformFilter *iface, const AM_MEDIA_TYPE *mt)
232
{
233 234
    ACMWrapperImpl *filter = impl_from_TransformFilter(iface);
    const WAVEFORMATEX *wfx = (WAVEFORMATEX *)mt->pbFormat;
235
    HACMSTREAM drv;
236
    MMRESULT res;
237

238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
    if (!IsEqualGUID(&mt->majortype, &MEDIATYPE_Audio) || !is_audio_subtype(&mt->subtype)
            || !IsEqualGUID(&mt->formattype, &FORMAT_WaveFormatEx) || !wfx
            || wfx->wFormatTag == WAVE_FORMAT_PCM || wfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
        return VFW_E_TYPE_NOT_ACCEPTED;

    CopyMediaType(&filter->tf.pmt, mt);
    filter->tf.pmt.subtype.Data1 = WAVE_FORMAT_PCM;
    filter->pWfOut = (WAVEFORMATEX *)filter->tf.pmt.pbFormat;
    filter->pWfOut->wFormatTag = WAVE_FORMAT_PCM;
    filter->pWfOut->wBitsPerSample = 16;
    filter->pWfOut->nBlockAlign = filter->pWfOut->wBitsPerSample * filter->pWfOut->nChannels / 8;
    filter->pWfOut->cbSize = 0;
    filter->pWfOut->nAvgBytesPerSec = filter->pWfOut->nChannels * filter->pWfOut->nSamplesPerSec
            * (filter->pWfOut->wBitsPerSample / 8);

    if ((res = acmStreamOpen(&drv, NULL, (WAVEFORMATEX *)wfx, filter->pWfOut, NULL, 0, 0, 0)))
254
    {
255 256 257
        ERR("Failed to open stream, error %u.\n", res);
        FreeMediaType(&filter->tf.pmt);
        return VFW_E_TYPE_NOT_ACCEPTED;
258 259
    }

260 261 262
    filter->has = drv;

    return S_OK;
263 264 265 266
}

static HRESULT WINAPI ACMWrapper_BreakConnect(TransformFilter *tf, PIN_DIRECTION dir)
{
267
    ACMWrapperImpl *This = impl_from_TransformFilter(tf);
268 269 270 271 272 273 274 275 276 277 278

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

280 281 282
    return S_OK;
}

283 284
static HRESULT WINAPI ACMWrapper_DecideBufferSize(TransformFilter *tf, IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *ppropInputRequest)
{
285
    ACMWrapperImpl *pACM = impl_from_TransformFilter(tf);
286 287 288 289 290 291 292 293 294 295 296 297 298 299
    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);
}

300
static const TransformFilterFuncTable ACMWrapper_FuncsTable = {
301 302 303 304
    .pfnDecideBufferSize = ACMWrapper_DecideBufferSize,
    .pfnReceive = ACMWrapper_Receive,
    .transform_connect_sink = acm_wrapper_connect_sink,
    .pfnBreakConnect = ACMWrapper_BreakConnect,
Christian Costa's avatar
Christian Costa committed
305 306
};

307
HRESULT ACMWrapper_create(IUnknown *outer, void **out)
308 309 310 311
{
    HRESULT hr;
    ACMWrapperImpl* This;

312
    *out = NULL;
313

314
    hr = strmbase_transform_create(sizeof(ACMWrapperImpl), outer, &CLSID_ACMWrapper,
315
            &ACMWrapper_FuncsTable, (IBaseFilter **)&This);
316 317 318 319

    if (FAILED(hr))
        return hr;

320
    *out = &This->tf.filter.IUnknown_inner;
321
    This->lasttime_real = This->lasttime_sent = -1;
322 323 324

    return hr;
}