smartteefilter.c 17.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
/*
 * Implementation of the SmartTee filter
 *
 * Copyright 2015 Damjan Jovanovic
 *
 * 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 <stdarg.h>

#define COBJMACROS

#include "windef.h"
#include "winbase.h"
#include "wtypes.h"
#include "wingdi.h"
#include "winuser.h"
#include "dshow.h"

#include "qcap_main.h"

#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(qcap);

typedef struct {
    BaseFilter filter;
40
    BaseInputPin sink;
41
    BaseOutputPin capture, preview;
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
} SmartTeeFilter;

static inline SmartTeeFilter *impl_from_BaseFilter(BaseFilter *filter)
{
    return CONTAINING_RECORD(filter, SmartTeeFilter, filter);
}

static inline SmartTeeFilter *impl_from_IBaseFilter(IBaseFilter *iface)
{
    BaseFilter *filter = CONTAINING_RECORD(iface, BaseFilter, IBaseFilter_iface);
    return impl_from_BaseFilter(filter);
}

static inline SmartTeeFilter *impl_from_BasePin(BasePin *pin)
{
    return impl_from_IBaseFilter(pin->pinInfo.pFilter);
}

static inline SmartTeeFilter *impl_from_IPin(IPin *iface)
{
    BasePin *bp = CONTAINING_RECORD(iface, BasePin, IPin_iface);
    return impl_from_IBaseFilter(bp->pinInfo.pFilter);
}

static HRESULT WINAPI SmartTeeFilter_Stop(IBaseFilter *iface)
{
    SmartTeeFilter *This = impl_from_IBaseFilter(iface);
69 70
    TRACE("(%p)\n", This);
    EnterCriticalSection(&This->filter.csFilter);
71
    This->filter.state = State_Stopped;
72 73
    LeaveCriticalSection(&This->filter.csFilter);
    return S_OK;
74 75 76 77 78 79 80 81 82 83 84
}

static HRESULT WINAPI SmartTeeFilter_Pause(IBaseFilter *iface)
{
    SmartTeeFilter *This = impl_from_IBaseFilter(iface);
    FIXME("(%p): stub\n", This);
    return E_NOTIMPL;
}

static HRESULT WINAPI SmartTeeFilter_Run(IBaseFilter *iface, REFERENCE_TIME tStart)
{
85 86
    SmartTeeFilter *This = impl_from_IBaseFilter(iface);
    HRESULT hr = S_OK;
87
    TRACE("(%p, %s)\n", This, wine_dbgstr_longlong(tStart));
88 89 90 91 92
    EnterCriticalSection(&This->filter.csFilter);
    if(This->filter.state != State_Running) {
        /* We share an allocator among all pins, an allocator can only get committed
         * once, state transitions occur in upstream order, and only output pins
         * commit allocators, so let the filter attached to the input pin worry about it. */
93
        if (This->sink.pin.pConnectedTo)
94 95 96 97 98 99
            This->filter.state = State_Running;
        else
            hr = VFW_E_NOT_CONNECTED;
    }
    LeaveCriticalSection(&This->filter.csFilter);
    return hr;
100 101 102
}

static const IBaseFilterVtbl SmartTeeFilterVtbl = {
103 104 105
    BaseFilterImpl_QueryInterface,
    BaseFilterImpl_AddRef,
    BaseFilterImpl_Release,
106 107 108 109 110 111 112 113
    BaseFilterImpl_GetClassID,
    SmartTeeFilter_Stop,
    SmartTeeFilter_Pause,
    SmartTeeFilter_Run,
    BaseFilterImpl_GetState,
    BaseFilterImpl_SetSyncSource,
    BaseFilterImpl_GetSyncSource,
    BaseFilterImpl_EnumPins,
114
    BaseFilterImpl_FindPin,
115 116 117 118 119
    BaseFilterImpl_QueryFilterInfo,
    BaseFilterImpl_JoinFilterGraph,
    BaseFilterImpl_QueryVendorInfo
};

120
static IPin *smart_tee_get_pin(BaseFilter *iface, unsigned int index)
121
{
122
    SmartTeeFilter *filter = impl_from_BaseFilter(iface);
123

124
    if (index == 0)
125
        return &filter->sink.pin.IPin_iface;
126
    else if (index == 1)
127
        return &filter->capture.pin.IPin_iface;
128
    else if (index == 2)
129
        return &filter->preview.pin.IPin_iface;
130
    return NULL;
131 132
}

133 134 135 136
static void smart_tee_destroy(BaseFilter *iface)
{
    SmartTeeFilter *filter = impl_from_BaseFilter(iface);

137
    strmbase_sink_cleanup(&filter->sink);
138
    strmbase_source_cleanup(&filter->capture);
139
    strmbase_source_cleanup(&filter->preview);
140 141 142 143
    strmbase_filter_cleanup(&filter->filter);
    CoTaskMemFree(filter);
}

144
static const BaseFilterFuncTable SmartTeeFilterFuncs = {
145
    .filter_get_pin = smart_tee_get_pin,
146
    .filter_destroy = smart_tee_destroy,
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
};

static ULONG WINAPI SmartTeeFilterInput_AddRef(IPin *iface)
{
    SmartTeeFilter *This = impl_from_IPin(iface);
    return IBaseFilter_AddRef(&This->filter.IBaseFilter_iface);
}

static ULONG WINAPI SmartTeeFilterInput_Release(IPin *iface)
{
    SmartTeeFilter *This = impl_from_IPin(iface);
    return IBaseFilter_Release(&This->filter.IBaseFilter_iface);
}


static const IPinVtbl SmartTeeFilterInputVtbl = {
    BaseInputPinImpl_QueryInterface,
    SmartTeeFilterInput_AddRef,
    SmartTeeFilterInput_Release,
    BaseInputPinImpl_Connect,
    BaseInputPinImpl_ReceiveConnection,
    BasePinImpl_Disconnect,
    BasePinImpl_ConnectedTo,
    BasePinImpl_ConnectionMediaType,
    BasePinImpl_QueryPinInfo,
    BasePinImpl_QueryDirection,
    BasePinImpl_QueryId,
    BasePinImpl_QueryAccept,
    BasePinImpl_EnumMediaTypes,
    BasePinImpl_QueryInternalConnections,
    BaseInputPinImpl_EndOfStream,
    BaseInputPinImpl_BeginFlush,
    BaseInputPinImpl_EndFlush,
    BaseInputPinImpl_NewSegment
};

static HRESULT WINAPI SmartTeeFilterInput_CheckMediaType(BasePin *base, const AM_MEDIA_TYPE *pmt)
{
    SmartTeeFilter *This = impl_from_BasePin(base);
186
    TRACE("(%p, AM_MEDIA_TYPE(%p))\n", This, pmt);
187
    dump_AM_MEDIA_TYPE(pmt);
188 189 190 191 192
    if (!pmt)
        return VFW_E_TYPE_NOT_ACCEPTED;
    /* We'll take any media type, but the output pins will later
     * struggle to connect downstream. */
    return S_OK;
193 194 195 196 197
}

static HRESULT WINAPI SmartTeeFilterInput_GetMediaType(BasePin *base, int iPosition, AM_MEDIA_TYPE *amt)
{
    SmartTeeFilter *This = impl_from_BasePin(base);
198 199 200 201 202
    HRESULT hr;
    TRACE("(%p)->(%d, %p)\n", This, iPosition, amt);
    if (iPosition)
        return S_FALSE;
    EnterCriticalSection(&This->filter.csFilter);
203 204 205
    if (This->sink.pin.pConnectedTo)
    {
        CopyMediaType(amt, &This->sink.pin.mtCurrent);
206
        hr = S_OK;
207 208
    }
    else
209 210 211
        hr = S_FALSE;
    LeaveCriticalSection(&This->filter.csFilter);
    return hr;
212 213
}

214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
static HRESULT copy_sample(IMediaSample *inputSample, IMemAllocator *allocator, IMediaSample **pOutputSample)
{
    REFERENCE_TIME startTime, endTime;
    BOOL haveStartTime = TRUE, haveEndTime = TRUE;
    IMediaSample *outputSample = NULL;
    BYTE *ptrIn, *ptrOut;
    AM_MEDIA_TYPE *mediaType = NULL;
    HRESULT hr;

    hr = IMediaSample_GetTime(inputSample, &startTime, &endTime);
    if (hr == S_OK)
        ;
    else if (hr == VFW_S_NO_STOP_TIME)
        haveEndTime = FALSE;
    else if (hr == VFW_E_SAMPLE_TIME_NOT_SET)
        haveStartTime = haveEndTime = FALSE;
    else
        goto end;

    hr = IMemAllocator_GetBuffer(allocator, &outputSample,
            haveStartTime ? &startTime : NULL, haveEndTime ? &endTime : NULL, 0);
    if (FAILED(hr)) goto end;
    if (IMediaSample_GetSize(outputSample) < IMediaSample_GetActualDataLength(inputSample)) {
        ERR("insufficient space in sample\n");
        hr = VFW_E_BUFFER_OVERFLOW;
        goto end;
    }

    hr = IMediaSample_SetTime(outputSample, haveStartTime ? &startTime : NULL, haveEndTime ? &endTime : NULL);
    if (FAILED(hr)) goto end;

    hr = IMediaSample_GetPointer(inputSample, &ptrIn);
    if (FAILED(hr)) goto end;
    hr = IMediaSample_GetPointer(outputSample, &ptrOut);
    if (FAILED(hr)) goto end;
    memcpy(ptrOut, ptrIn, IMediaSample_GetActualDataLength(inputSample));
    IMediaSample_SetActualDataLength(outputSample, IMediaSample_GetActualDataLength(inputSample));

    hr = IMediaSample_SetDiscontinuity(outputSample, IMediaSample_IsDiscontinuity(inputSample) == S_OK);
    if (FAILED(hr)) goto end;

    haveStartTime = haveEndTime = TRUE;
    hr = IMediaSample_GetMediaTime(inputSample, &startTime, &endTime);
    if (hr == S_OK)
        ;
    else if (hr == VFW_S_NO_STOP_TIME)
        haveEndTime = FALSE;
    else if (hr == VFW_E_MEDIA_TIME_NOT_SET)
        haveStartTime = haveEndTime = FALSE;
    else
        goto end;
    hr = IMediaSample_SetMediaTime(outputSample, haveStartTime ? &startTime : NULL, haveEndTime ? &endTime : NULL);
    if (FAILED(hr)) goto end;

    hr = IMediaSample_GetMediaType(inputSample, &mediaType);
    if (FAILED(hr)) goto end;
    if (hr == S_OK) {
        hr = IMediaSample_SetMediaType(outputSample, mediaType);
        if (FAILED(hr)) goto end;
    }

    hr = IMediaSample_SetPreroll(outputSample, IMediaSample_IsPreroll(inputSample) == S_OK);
    if (FAILED(hr)) goto end;

    hr = IMediaSample_SetSyncPoint(outputSample, IMediaSample_IsSyncPoint(inputSample) == S_OK);
    if (FAILED(hr)) goto end;

end:
    if (mediaType)
        DeleteMediaType(mediaType);
    if (FAILED(hr) && outputSample) {
        IMediaSample_Release(outputSample);
        outputSample = NULL;
    }
    *pOutputSample = outputSample;
    return hr;
}

static HRESULT WINAPI SmartTeeFilterInput_Receive(BaseInputPin *base, IMediaSample *inputSample)
293 294
{
    SmartTeeFilter *This = impl_from_BasePin(&base->pin);
295 296 297 298 299 300 301 302 303 304 305 306 307
    IMediaSample *captureSample = NULL;
    IMediaSample *previewSample = NULL;
    HRESULT hrCapture = VFW_E_NOT_CONNECTED, hrPreview = VFW_E_NOT_CONNECTED;

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

    /* Modifying the image coming out of one pin doesn't modify the image
     * coming out of the other. MSDN claims the filter doesn't copy,
     * but unless it somehow uses copy-on-write, I just don't see how
     * that's possible. */

    /* FIXME: we should ideally do each of these in a separate thread */
    EnterCriticalSection(&This->filter.csFilter);
308 309
    if (This->capture.pin.pConnectedTo)
        hrCapture = copy_sample(inputSample, This->capture.pAllocator, &captureSample);
310 311
    LeaveCriticalSection(&This->filter.csFilter);
    if (SUCCEEDED(hrCapture))
312
        hrCapture = BaseOutputPinImpl_Deliver(&This->capture, captureSample);
313 314 315 316
    if (captureSample)
        IMediaSample_Release(captureSample);

    EnterCriticalSection(&This->filter.csFilter);
317 318
    if (This->preview.pin.pConnectedTo)
        hrPreview = copy_sample(inputSample, This->preview.pAllocator, &previewSample);
319 320 321 322 323
    LeaveCriticalSection(&This->filter.csFilter);
    /* No timestamps on preview stream: */
    if (SUCCEEDED(hrPreview))
        hrPreview = IMediaSample_SetTime(previewSample, NULL, NULL);
    if (SUCCEEDED(hrPreview))
324
        hrPreview = BaseOutputPinImpl_Deliver(&This->preview, previewSample);
325 326 327 328 329 330 331 332
    if (previewSample)
        IMediaSample_Release(previewSample);

    /* FIXME: how to merge the HRESULTs from the 2 pins? */
    if (SUCCEEDED(hrCapture))
        return hrCapture;
    else
        return hrPreview;
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
}

static const BaseInputPinFuncTable SmartTeeFilterInputFuncs = {
    {
        SmartTeeFilterInput_CheckMediaType,
        SmartTeeFilterInput_GetMediaType
    },
    SmartTeeFilterInput_Receive
};

static ULONG WINAPI SmartTeeFilterCapture_AddRef(IPin *iface)
{
    SmartTeeFilter *This = impl_from_IPin(iface);
    return IBaseFilter_AddRef(&This->filter.IBaseFilter_iface);
}

static ULONG WINAPI SmartTeeFilterCapture_Release(IPin *iface)
{
    SmartTeeFilter *This = impl_from_IPin(iface);
    return IBaseFilter_Release(&This->filter.IBaseFilter_iface);
}

355 356 357 358 359 360
static HRESULT WINAPI SmartTeeFilterCapture_EnumMediaTypes(IPin *iface, IEnumMediaTypes **ppEnum)
{
    SmartTeeFilter *This = impl_from_IPin(iface);
    HRESULT hr;
    TRACE("(%p)->(%p)\n", This, ppEnum);
    EnterCriticalSection(&This->filter.csFilter);
361
    if (This->sink.pin.pConnectedTo)
362
        hr = BasePinImpl_EnumMediaTypes(iface, ppEnum);
363
    else
364 365 366 367 368
        hr = VFW_E_NOT_CONNECTED;
    LeaveCriticalSection(&This->filter.csFilter);
    return hr;
}

369 370 371 372 373 374 375 376 377 378 379 380 381
static const IPinVtbl SmartTeeFilterCaptureVtbl = {
    BaseOutputPinImpl_QueryInterface,
    SmartTeeFilterCapture_AddRef,
    SmartTeeFilterCapture_Release,
    BaseOutputPinImpl_Connect,
    BaseOutputPinImpl_ReceiveConnection,
    BaseOutputPinImpl_Disconnect,
    BasePinImpl_ConnectedTo,
    BasePinImpl_ConnectionMediaType,
    BasePinImpl_QueryPinInfo,
    BasePinImpl_QueryDirection,
    BasePinImpl_QueryId,
    BasePinImpl_QueryAccept,
382
    SmartTeeFilterCapture_EnumMediaTypes,
383 384 385 386 387 388 389
    BasePinImpl_QueryInternalConnections,
    BaseOutputPinImpl_EndOfStream,
    BaseOutputPinImpl_BeginFlush,
    BaseOutputPinImpl_EndFlush,
    BasePinImpl_NewSegment
};

390 391 392 393 394 395
static HRESULT WINAPI SmartTeeFilterCapture_CheckMediaType(BasePin *base, const AM_MEDIA_TYPE *amt)
{
    FIXME("(%p) stub\n", base);
    return S_OK;
}

396 397 398
static HRESULT WINAPI SmartTeeFilterCapture_GetMediaType(BasePin *base, int iPosition, AM_MEDIA_TYPE *amt)
{
    SmartTeeFilter *This = impl_from_BasePin(base);
399 400
    TRACE("(%p, %d, %p)\n", This, iPosition, amt);
    if (iPosition == 0) {
401
        CopyMediaType(amt, &This->sink.pin.mtCurrent);
402 403 404
        return S_OK;
    } else
        return S_FALSE;
405 406
}

407
static HRESULT WINAPI SmartTeeFilterCapture_DecideAllocator(BaseOutputPin *base, IMemInputPin *pPin, IMemAllocator **pAlloc)
408 409
{
    SmartTeeFilter *This = impl_from_BasePin(&base->pin);
410
    TRACE("(%p, %p, %p)\n", This, pPin, pAlloc);
411 412 413
    *pAlloc = This->sink.pAllocator;
    IMemAllocator_AddRef(This->sink.pAllocator);
    return IMemInputPin_NotifyAllocator(pPin, This->sink.pAllocator, TRUE);
414 415 416 417
}

static const BaseOutputPinFuncTable SmartTeeFilterCaptureFuncs = {
    {
418
        SmartTeeFilterCapture_CheckMediaType,
419 420
        SmartTeeFilterCapture_GetMediaType
    },
421
    BaseOutputPinImpl_AttemptConnection,
422 423
    NULL,
    SmartTeeFilterCapture_DecideAllocator,
424 425 426 427 428 429 430 431 432 433 434 435 436 437
};

static ULONG WINAPI SmartTeeFilterPreview_AddRef(IPin *iface)
{
    SmartTeeFilter *This = impl_from_IPin(iface);
    return IBaseFilter_AddRef(&This->filter.IBaseFilter_iface);
}

static ULONG WINAPI SmartTeeFilterPreview_Release(IPin *iface)
{
    SmartTeeFilter *This = impl_from_IPin(iface);
    return IBaseFilter_Release(&This->filter.IBaseFilter_iface);
}

438 439 440 441 442 443
static HRESULT WINAPI SmartTeeFilterPreview_EnumMediaTypes(IPin *iface, IEnumMediaTypes **ppEnum)
{
    SmartTeeFilter *This = impl_from_IPin(iface);
    HRESULT hr;
    TRACE("(%p)->(%p)\n", This, ppEnum);
    EnterCriticalSection(&This->filter.csFilter);
444
    if (This->sink.pin.pConnectedTo)
445
        hr = BasePinImpl_EnumMediaTypes(iface, ppEnum);
446
    else
447 448 449 450 451
        hr = VFW_E_NOT_CONNECTED;
    LeaveCriticalSection(&This->filter.csFilter);
    return hr;
}

452 453 454 455 456 457 458 459 460 461 462 463 464
static const IPinVtbl SmartTeeFilterPreviewVtbl = {
    BaseOutputPinImpl_QueryInterface,
    SmartTeeFilterPreview_AddRef,
    SmartTeeFilterPreview_Release,
    BaseOutputPinImpl_Connect,
    BaseOutputPinImpl_ReceiveConnection,
    BaseOutputPinImpl_Disconnect,
    BasePinImpl_ConnectedTo,
    BasePinImpl_ConnectionMediaType,
    BasePinImpl_QueryPinInfo,
    BasePinImpl_QueryDirection,
    BasePinImpl_QueryId,
    BasePinImpl_QueryAccept,
465
    SmartTeeFilterPreview_EnumMediaTypes,
466 467 468 469 470 471 472
    BasePinImpl_QueryInternalConnections,
    BaseOutputPinImpl_EndOfStream,
    BaseOutputPinImpl_BeginFlush,
    BaseOutputPinImpl_EndFlush,
    BasePinImpl_NewSegment
};

473 474 475 476 477 478
static HRESULT WINAPI SmartTeeFilterPreview_CheckMediaType(BasePin *base, const AM_MEDIA_TYPE *amt)
{
    FIXME("(%p) stub\n", base);
    return S_OK;
}

479 480 481
static HRESULT WINAPI SmartTeeFilterPreview_GetMediaType(BasePin *base, int iPosition, AM_MEDIA_TYPE *amt)
{
    SmartTeeFilter *This = impl_from_BasePin(base);
482 483
    TRACE("(%p, %d, %p)\n", This, iPosition, amt);
    if (iPosition == 0) {
484
        CopyMediaType(amt, &This->sink.pin.mtCurrent);
485 486 487
        return S_OK;
    } else
        return S_FALSE;
488 489
}

490
static HRESULT WINAPI SmartTeeFilterPreview_DecideAllocator(BaseOutputPin *base, IMemInputPin *pPin, IMemAllocator **pAlloc)
491 492
{
    SmartTeeFilter *This = impl_from_BasePin(&base->pin);
493
    TRACE("(%p, %p, %p)\n", This, pPin, pAlloc);
494 495 496
    *pAlloc = This->sink.pAllocator;
    IMemAllocator_AddRef(This->sink.pAllocator);
    return IMemInputPin_NotifyAllocator(pPin, This->sink.pAllocator, TRUE);
497 498 499 500
}

static const BaseOutputPinFuncTable SmartTeeFilterPreviewFuncs = {
    {
501
        SmartTeeFilterPreview_CheckMediaType,
502 503
        SmartTeeFilterPreview_GetMediaType
    },
504
    BaseOutputPinImpl_AttemptConnection,
505 506
    NULL,
    SmartTeeFilterPreview_DecideAllocator,
507 508 509 510 511 512 513 514 515 516 517
};
IUnknown* WINAPI QCAP_createSmartTeeFilter(IUnknown *outer, HRESULT *phr)
{
    PIN_INFO inputPinInfo  = {NULL, PINDIR_INPUT,  {'I','n','p','u','t',0}};
    PIN_INFO capturePinInfo = {NULL, PINDIR_OUTPUT, {'C','a','p','t','u','r','e',0}};
    PIN_INFO previewPinInfo = {NULL, PINDIR_OUTPUT, {'P','r','e','v','i','e','w',0}};
    HRESULT hr;
    SmartTeeFilter *This = NULL;

    This = CoTaskMemAlloc(sizeof(*This));
    if (This == NULL) {
518 519
        *phr = E_OUTOFMEMORY;
        return NULL;
520 521 522
    }
    memset(This, 0, sizeof(*This));

523
    strmbase_filter_init(&This->filter, &SmartTeeFilterVtbl, outer, &CLSID_SmartTee,
524 525 526
            (DWORD_PTR)(__FILE__ ": SmartTeeFilter.csFilter"), &SmartTeeFilterFuncs);

    inputPinInfo.pFilter = &This->filter.IBaseFilter_iface;
527 528
    strmbase_sink_init(&This->sink, &SmartTeeFilterInputVtbl, &inputPinInfo,
            &SmartTeeFilterInputFuncs, &This->filter.csFilter, NULL);
529
    hr = CoCreateInstance(&CLSID_MemoryAllocator, NULL, CLSCTX_INPROC_SERVER,
530
            &IID_IMemAllocator, (void**)&This->sink.pAllocator);
531
    if (FAILED(hr))
532 533 534 535 536
    {
        *phr = hr;
        strmbase_filter_cleanup(&This->filter);
        return NULL;
    }
537 538

    capturePinInfo.pFilter = &This->filter.IBaseFilter_iface;
539 540
    strmbase_source_init(&This->capture, &SmartTeeFilterCaptureVtbl, &capturePinInfo,
            &SmartTeeFilterCaptureFuncs, &This->filter.csFilter);
541 542

    previewPinInfo.pFilter = &This->filter.IBaseFilter_iface;
543 544
    strmbase_source_init(&This->preview, &SmartTeeFilterPreviewVtbl, &previewPinInfo,
            &SmartTeeFilterPreviewFuncs, &This->filter.csFilter);
545

546 547
    *phr = S_OK;
    return &This->filter.IUnknown_inner;
548
}