winegstreamer: Add gstreamer YUV->RGB transform filter.
[wine] / dlls / winegstreamer / gsttffilter.c
1 /*
2  * GStreamer wrapper filter
3  *
4  * Copyright 2010 Maarten Lankhorst for CodeWeavers
5  * Copyright 2010 Aric Stewart for CodeWeavers
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  *
21  */
22
23 #include "config.h"
24
25 #include <gst/app/gstappsink.h>
26 #include <gst/app/gstappsrc.h>
27 #include <gst/app/gstappbuffer.h>
28
29 #include "gst_private.h"
30 #include "gst_guids.h"
31
32 #include "uuids.h"
33 #include "mmreg.h"
34 #include "windef.h"
35 #include "winbase.h"
36 #include "dshow.h"
37 #include "strmif.h"
38 #include "vfwmsgs.h"
39 #include "dvdmedia.h"
40 #include "ks.h"
41 #include "ksmedia.h"
42 #include "msacm.h"
43
44 #include <assert.h>
45
46 #include "wine/unicode.h"
47 #include "wine/debug.h"
48
49 #include "initguid.h"
50
51 WINE_DEFAULT_DEBUG_CHANNEL(gstreamer);
52
53 struct typeinfo {
54     GstCaps *caps;
55     const char *type;
56 };
57
58 static const IBaseFilterVtbl GSTTf_Vtbl;
59
60 static gboolean match_element(GstPluginFeature *feature, gpointer gdata) {
61     struct typeinfo *data = (struct typeinfo*)gdata;
62     GstElementFactory *factory;
63     const GList *list;
64
65     if (!GST_IS_ELEMENT_FACTORY(feature))
66         return FALSE;
67     factory = GST_ELEMENT_FACTORY(feature);
68     if (!strstr(gst_element_factory_get_klass(factory), data->type))
69         return FALSE;
70     for (list = gst_element_factory_get_static_pad_templates(factory); list; list = list->next) {
71         GstStaticPadTemplate *pad = (GstStaticPadTemplate*)list->data;
72         GstCaps *caps;
73         gboolean ret;
74         if (pad->direction != GST_PAD_SINK)
75             continue;
76         caps = gst_static_caps_get(&pad->static_caps);
77         ret = gst_caps_is_always_compatible(caps, data->caps);
78         gst_caps_unref(caps);
79         if (ret)
80             return TRUE;
81     }
82     return FALSE;
83 }
84
85 static const char *Gstreamer_FindMatch(const char *strcaps)
86 {
87     struct typeinfo data;
88     GList *list, *copy;
89     guint bestrank = 0;
90     GstElementFactory *bestfactory = NULL;
91     GstCaps *caps = gst_caps_from_string(strcaps);
92
93     data.caps = caps;
94     data.type = "Decoder";
95     copy = gst_default_registry_feature_filter(match_element, 0, &data);
96     for (list = copy; list; list = list->next) {
97         GstElementFactory *factory = (GstElementFactory*)list->data;
98         guint rank;
99         rank = gst_plugin_feature_get_rank(GST_PLUGIN_FEATURE(factory));
100         if (rank > bestrank || !bestrank) {
101             bestrank = rank;
102             bestfactory = factory;
103         }
104     }
105     gst_caps_unref(caps);
106     g_list_free(copy);
107
108     if (!bestfactory) {
109         FIXME("Could not find plugin for %s\n", strcaps);
110         return NULL;
111     }
112     return gst_plugin_feature_get_name(GST_PLUGIN_FEATURE(bestfactory));
113 }
114
115 typedef struct GstTfImpl {
116     TransformFilter tf;
117     IUnknown *seekthru_unk;
118     const char *gstreamer_name;
119     GstElement *filter;
120     GstPad *my_src, *my_sink, *their_src, *their_sink;
121     LONG cbBuffer;
122 } GstTfImpl;
123
124 static HRESULT WINAPI Gstreamer_transform_ProcessBegin(TransformFilter *iface) {
125     GstTfImpl *This = (GstTfImpl*)iface;
126     int ret;
127
128     ret = gst_element_set_state(This->filter, GST_STATE_PLAYING);
129     TRACE("Returned: %i\n", ret);
130     return S_OK;
131 }
132
133 static HRESULT WINAPI Gstreamer_transform_DecideBufferSize(TransformFilter *tf, IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *ppropInputRequest)
134 {
135     GstTfImpl *This = (GstTfImpl*)tf;
136     ALLOCATOR_PROPERTIES actual;
137
138     if (!ppropInputRequest->cbAlign)
139         ppropInputRequest->cbAlign = 1;
140
141     ppropInputRequest->cbBuffer = This->cbBuffer;
142
143     if (!ppropInputRequest->cBuffers)
144         ppropInputRequest->cBuffers = 1;
145
146     return IMemAllocator_SetProperties(pAlloc, ppropInputRequest, &actual);
147 }
148
149 static void release_sample(void *data) {
150     TRACE("Releasing %p\n", data);
151     IMediaSample_Release((IMediaSample *)data);
152 }
153
154 static GstFlowReturn got_data(GstPad *pad, GstBuffer *buf) {
155     GstTfImpl *This = gst_pad_get_element_private(pad);
156     IMediaSample *sample = GST_APP_BUFFER(buf)->priv;
157     REFERENCE_TIME tStart, tStop;
158     HRESULT hr;
159
160     if (GST_BUFFER_TIMESTAMP_IS_VALID(buf) &&
161         GST_BUFFER_DURATION_IS_VALID(buf)) {
162         tStart = buf->timestamp / 100;
163         tStop = tStart + buf->duration / 100;
164         IMediaSample_SetTime(sample, &tStart, &tStop);
165     }
166     else
167         IMediaSample_SetTime(sample, NULL, NULL);
168
169     IMediaSample_SetDiscontinuity(sample, GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_DISCONT));
170     IMediaSample_SetPreroll(sample, GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_PREROLL));
171     IMediaSample_SetSyncPoint(sample, !GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_DELTA_UNIT));
172
173     hr = BaseOutputPinImpl_Deliver((BaseOutputPin*)This->tf.ppPins[1], sample);
174     gst_buffer_unref(buf);
175     if (FAILED(hr))
176         return GST_FLOW_WRONG_STATE;
177     if (hr != S_OK)
178         return GST_FLOW_RESEND;
179     return GST_FLOW_OK;
180 }
181
182 static GstFlowReturn request_buffer(GstPad *pad, guint64 ofs, guint size, GstCaps *caps, GstBuffer **buf) {
183     GstTfImpl *This = gst_pad_get_element_private(pad);
184     IMediaSample *sample;
185     BYTE *ptr;
186     HRESULT hr;
187     TRACE("Requesting buffer\n");
188
189     hr = BaseOutputPinImpl_GetDeliveryBuffer((BaseOutputPin*)This->tf.ppPins[1], &sample, NULL, NULL, 0);
190     if (FAILED(hr)) {
191         ERR("Could not get output buffer: %08x\n", hr);
192         return GST_FLOW_WRONG_STATE;
193     }
194     IMediaSample_SetActualDataLength(sample, size);
195     IMediaSample_GetPointer(sample, &ptr);
196     *buf = gst_app_buffer_new(ptr, size, release_sample, sample);
197
198     if (!*buf) {
199         IMediaSample_Release(sample);
200         ERR("Out of memory\n");
201         return GST_FLOW_ERROR;
202     }
203     if (!caps)
204         caps = gst_pad_get_caps_reffed(This->my_sink);
205     gst_buffer_set_caps(*buf, caps);
206     return GST_FLOW_OK;
207 }
208
209 static HRESULT WINAPI Gstreamer_transform_ProcessData(TransformFilter *iface, IMediaSample *sample) {
210     GstTfImpl *This = (GstTfImpl*)iface;
211     REFERENCE_TIME tStart, tStop;
212     BYTE *data;
213     GstBuffer *buf;
214     HRESULT hr;
215     int ret;
216     TRACE("Reading %p\n", sample);
217
218     EnterCriticalSection(&This->tf.filter.csFilter);
219     IMediaSample_GetPointer(sample, &data);
220     buf = gst_app_buffer_new(data, IMediaSample_GetActualDataLength(sample), release_sample, sample);
221     if (!buf) {
222         LeaveCriticalSection(&This->tf.filter.csFilter);
223         return S_OK;
224     }
225     gst_buffer_set_caps(buf, gst_pad_get_caps_reffed(This->my_src));
226     IMediaSample_AddRef(sample);
227     buf->duration = buf->timestamp = -1;
228     hr = IMediaSample_GetTime(sample, &tStart, &tStop);
229     if (SUCCEEDED(hr)) {
230         buf->timestamp = tStart * 100;
231         if (hr == S_OK)
232             buf->duration = (tStop - tStart)*100;
233     }
234     if (IMediaSample_IsDiscontinuity(sample) == S_OK)
235         GST_BUFFER_FLAG_SET(buf, GST_BUFFER_FLAG_DISCONT);
236     if (IMediaSample_IsPreroll(sample) == S_OK)
237         GST_BUFFER_FLAG_SET(buf, GST_BUFFER_FLAG_PREROLL);
238     if (IMediaSample_IsSyncPoint(sample) != S_OK)
239         GST_BUFFER_FLAG_SET(buf, GST_BUFFER_FLAG_DELTA_UNIT);
240     LeaveCriticalSection(&This->tf.filter.csFilter);
241     ret = gst_pad_push(This->my_src, buf);
242     if (ret)
243         WARN("Sending returned: %i\n", ret);
244     if (ret == GST_FLOW_ERROR)
245         return E_FAIL;
246     if (ret == GST_FLOW_WRONG_STATE)
247         return VFW_E_WRONG_STATE;
248     if (ret == GST_FLOW_RESEND)
249         return S_FALSE;
250     return S_OK;
251 }
252
253 static HRESULT WINAPI Gstreamer_transform_ProcessEnd(TransformFilter *iface) {
254     GstTfImpl *This = (GstTfImpl*)iface;
255     int ret;
256
257     ret = gst_element_set_state(This->filter, GST_STATE_PAUSED);
258     TRACE("Returned: %i\n", ret);
259     return S_OK;
260 }
261
262 static void Gstreamer_transform_pad_added(GstElement *filter, GstPad *pad, GstTfImpl *This)
263 {
264     int ret;
265     if (!GST_PAD_IS_SRC(pad))
266         return;
267
268     ret = gst_pad_link(pad, This->my_sink);
269     if (ret < 0)
270         WARN("Failed to link with %i\n", ret);
271     This->their_src = pad;
272
273     gst_pad_set_active(pad, TRUE);
274     gst_pad_set_active(This->my_sink, TRUE);
275 }
276
277 static HRESULT Gstreamer_transform_ConnectInput(GstTfImpl *This, const AM_MEDIA_TYPE *amt, GstCaps *capsin, GstCaps *capsout) {
278     GstIterator *it;
279     int done = 0, found = 0, ret;
280
281     This->filter = gst_element_factory_make(This->gstreamer_name, NULL);
282     if (!This->filter) {
283         FIXME("Could not make %s filter\n", This->gstreamer_name);
284         return E_FAIL;
285     }
286     This->my_src = gst_pad_new(NULL, GST_PAD_SRC);
287     gst_pad_set_element_private (This->my_src, This);
288
289     This->my_sink = gst_pad_new(NULL, GST_PAD_SINK);
290     gst_pad_set_chain_function(This->my_sink, got_data);
291     gst_pad_set_bufferalloc_function(This->my_sink, request_buffer);
292     gst_pad_set_element_private (This->my_sink, This);
293
294     ret = gst_pad_set_caps(This->my_src, capsin);
295     if (ret < 0) {
296         WARN("Failed to set caps on own source with %i\n", ret);
297         return E_FAIL;
298     }
299
300     ret = gst_pad_set_caps(This->my_sink, capsout);
301     if (ret < 0) {
302         WARN("Failed to set caps on own sink with %i\n", ret);
303         return E_FAIL;
304     }
305
306     it = gst_element_iterate_sink_pads(This->filter);
307     while (!done) {
308         gpointer item;
309
310         switch (gst_iterator_next(it, &item)) {
311         case GST_ITERATOR_RESYNC:
312             gst_iterator_resync (it);
313             break;
314         case GST_ITERATOR_OK:
315             This->their_sink = item;
316         case GST_ITERATOR_ERROR:
317         case GST_ITERATOR_DONE:
318             done = 1;
319             break;
320         }
321     }
322     gst_iterator_free(it);
323     if (!This->their_sink) {
324         ERR("Could not find sink on filter %s\n", This->gstreamer_name);
325         return E_FAIL;
326     }
327
328     it = gst_element_iterate_src_pads(This->filter);
329     gst_iterator_resync(it);
330     done = 0;
331     while (!done) {
332         gpointer item;
333
334         switch (gst_iterator_next(it, &item)) {
335         case GST_ITERATOR_RESYNC:
336             gst_iterator_resync (it);
337             break;
338         case GST_ITERATOR_OK:
339             This->their_src = item;
340         case GST_ITERATOR_ERROR:
341         case GST_ITERATOR_DONE:
342             done = 1;
343             break;
344         }
345     }
346     gst_iterator_free(it);
347     found = !!This->their_src;
348     if (!found)
349         g_signal_connect(This->filter, "pad-added", G_CALLBACK(Gstreamer_transform_pad_added), This);
350     ret = gst_pad_link(This->my_src, This->their_sink);
351     if (ret < 0) {
352         WARN("Failed to link with %i\n", ret);
353         return E_FAIL;
354     }
355
356     if (found)
357         Gstreamer_transform_pad_added(This->filter, This->their_src, This);
358
359     if (!gst_pad_is_linked(This->my_sink))
360         return E_FAIL;
361
362     TRACE("Connected\n");
363     return S_OK;
364 }
365
366 static HRESULT WINAPI Gstreamer_transform_Cleanup(TransformFilter *tf, PIN_DIRECTION dir) {
367     GstTfImpl *This = (GstTfImpl*)tf;
368
369     if (dir == PINDIR_INPUT)
370     {
371         if (This->filter) {
372             gst_element_set_state(This->filter, GST_STATE_NULL);
373             gst_object_unref(This->filter);
374         }
375         This->filter = NULL;
376         if (This->my_src) {
377             gst_pad_unlink(This->my_src, This->their_sink);
378             gst_object_unref(This->my_src);
379         }
380         if (This->my_sink) {
381             gst_pad_unlink(This->their_src, This->my_sink);
382             gst_object_unref(This->my_sink);
383         }
384         This->my_sink = This->my_src = This->their_sink = This->their_src = NULL;
385         FIXME("%p stub\n", This);
386     }
387     return S_OK;
388 }
389
390 static HRESULT WINAPI Gstreamer_transform_EndOfStream(TransformFilter *iface) {
391     GstTfImpl *This = (GstTfImpl*)iface;
392     TRACE("%p\n", This);
393
394     gst_pad_push_event(This->my_src, gst_event_new_eos());
395     return S_OK;
396 }
397
398 static HRESULT WINAPI Gstreamer_transform_BeginFlush(TransformFilter *iface) {
399     GstTfImpl *This = (GstTfImpl*)iface;
400     TRACE("%p\n", This);
401
402     gst_pad_push_event(This->my_src, gst_event_new_flush_start());
403     return S_OK;
404 }
405
406 static HRESULT WINAPI Gstreamer_transform_EndFlush(TransformFilter *iface) {
407     GstTfImpl *This = (GstTfImpl*)iface;
408     TRACE("%p\n", This);
409
410     gst_pad_push_event(This->my_src, gst_event_new_flush_stop());
411     return S_OK;
412 }
413
414 static HRESULT WINAPI Gstreamer_transform_NewSegment(TransformFilter *iface, REFERENCE_TIME tStart, REFERENCE_TIME tStop, double dRate) {
415     GstTfImpl *This = (GstTfImpl*)iface;
416     TRACE("%p\n", This);
417
418     gst_pad_push_event(This->my_src, gst_event_new_new_segment_full(1,
419                        1.0, dRate, GST_FORMAT_TIME, tStart*100, tStop <= tStart ? -1 : tStop * 100, tStart*100));
420     return S_OK;
421 }
422
423 static HRESULT Gstreamer_transform_create(IUnknown *punkout, const CLSID *clsid, const char *name, const TransformFilterFuncTable *vtbl, void **obj)
424 {
425     GstTfImpl *This;
426
427     if (FAILED(TransformFilter_Construct(&GSTTf_Vtbl, sizeof(GstTfImpl), clsid, vtbl, (IBaseFilter**)&This)))
428         return E_OUTOFMEMORY;
429     else
430     {
431         ISeekingPassThru *passthru;
432         CoCreateInstance(&CLSID_SeekingPassThru, (IUnknown*)This, CLSCTX_INPROC_SERVER, &IID_IUnknown, (void**)&This->seekthru_unk);
433         IUnknown_QueryInterface(This->seekthru_unk, &IID_ISeekingPassThru, (void**)&passthru);
434         ISeekingPassThru_Init(passthru, FALSE, (IPin*)This->tf.ppPins[0]);
435         ISeekingPassThru_Release(passthru);
436     }
437
438     This->gstreamer_name = name;
439     *obj = This;
440
441     return S_OK;
442 }
443
444 static HRESULT WINAPI Gstreamer_YUV_QueryConnect(TransformFilter *iface, const AM_MEDIA_TYPE *amt) {
445     GstTfImpl *This = (GstTfImpl*)iface;
446     TRACE("%p %p\n", This, amt);
447     dump_AM_MEDIA_TYPE(amt);
448
449     if (!IsEqualGUID(&amt->majortype, &MEDIATYPE_Video) ||
450         (!IsEqualGUID(&amt->formattype, &FORMAT_VideoInfo) &&
451          !IsEqualGUID(&amt->formattype, &FORMAT_VideoInfo2)))
452         return S_FALSE;
453     if (memcmp(&amt->subtype.Data2, &MEDIATYPE_Video.Data2, sizeof(GUID) - sizeof(amt->subtype.Data1)))
454         return S_FALSE;
455     switch (amt->subtype.Data1) {
456         case mmioFOURCC('I','4','2','0'):
457         case mmioFOURCC('Y','V','1','2'):
458         case mmioFOURCC('N','V','1','2'):
459         case mmioFOURCC('N','V','2','1'):
460         case mmioFOURCC('Y','U','Y','2'):
461         case mmioFOURCC('Y','V','Y','U'):
462             return S_OK;
463         default:
464             WARN("Unhandled fourcc %s\n", debugstr_an((char*)&amt->subtype.Data1, 4));
465             return S_FALSE;
466     }
467 }
468
469 static HRESULT WINAPI Gstreamer_YUV_ConnectInput(TransformFilter *tf, PIN_DIRECTION dir, IPin *pin)
470 {
471     return S_OK;
472 }
473
474 static HRESULT WINAPI Gstreamer_YUV_SetMediaType(TransformFilter *tf, PIN_DIRECTION dir,  const AM_MEDIA_TYPE *amt) {
475     GstTfImpl *This = (GstTfImpl*)tf;
476     GstCaps *capsin, *capsout;
477     AM_MEDIA_TYPE *outpmt = &This->tf.pmt;
478     HRESULT hr;
479     int avgtime;
480     DWORD width, height;
481
482     if (dir != PINDIR_INPUT)
483         return S_OK;
484
485     if (Gstreamer_YUV_QueryConnect(&This->tf, amt) == S_FALSE || !amt->pbFormat)
486         return E_FAIL;
487
488     FreeMediaType(outpmt);
489     CopyMediaType(outpmt, amt);
490
491     if (IsEqualGUID(&amt->formattype, &FORMAT_VideoInfo)) {
492         VIDEOINFOHEADER *vih = (VIDEOINFOHEADER*)outpmt->pbFormat;
493         avgtime = vih->AvgTimePerFrame;
494         width = vih->bmiHeader.biWidth;
495         height = vih->bmiHeader.biHeight;
496         if ((LONG)vih->bmiHeader.biHeight > 0)
497             vih->bmiHeader.biHeight = -vih->bmiHeader.biHeight;
498         vih->bmiHeader.biBitCount = 24;
499         vih->bmiHeader.biCompression = BI_RGB;
500     } else {
501         VIDEOINFOHEADER2 *vih = (VIDEOINFOHEADER2*)outpmt->pbFormat;
502         avgtime = vih->AvgTimePerFrame;
503         width = vih->bmiHeader.biWidth;
504         height = vih->bmiHeader.biHeight;
505         if ((LONG)vih->bmiHeader.biHeight > 0)
506             vih->bmiHeader.biHeight = -vih->bmiHeader.biHeight;
507         vih->bmiHeader.biBitCount = 24;
508         vih->bmiHeader.biCompression = BI_RGB;
509     }
510     if (!avgtime)
511         avgtime = 10000000 / 30;
512
513     outpmt->subtype = MEDIASUBTYPE_RGB24;
514
515     capsin = gst_caps_new_simple("video/x-raw-yuv",
516                                  "format", GST_TYPE_FOURCC, amt->subtype.Data1,
517                                  "width", G_TYPE_INT, width,
518                                  "height", G_TYPE_INT, height,
519                                  "framerate", GST_TYPE_FRACTION, 10000000, avgtime,
520                                  NULL);
521     capsout = gst_caps_new_simple("video/x-raw-rgb",
522                                   "endianness", G_TYPE_INT, 4321,
523                                   "width", G_TYPE_INT, width,
524                                   "height", G_TYPE_INT, height,
525                                   "framerate", GST_TYPE_FRACTION, 10000000, avgtime,
526                                   "bpp", G_TYPE_INT, 24,
527                                   "depth", G_TYPE_INT, 24,
528                                   "red_mask", G_TYPE_INT, 0xff,
529                                   "green_mask", G_TYPE_INT, 0xff00,
530                                   "blue_mask", G_TYPE_INT, 0xff0000,
531                                    NULL);
532
533     hr = Gstreamer_transform_ConnectInput(This, amt, capsin, capsout);
534     gst_caps_unref(capsin);
535     gst_caps_unref(capsout);
536
537     This->cbBuffer = width * height * 4;
538     return hr;
539 }
540
541 static const TransformFilterFuncTable Gstreamer_YUV_vtbl = {
542     Gstreamer_transform_DecideBufferSize,
543     Gstreamer_transform_ProcessBegin,
544     Gstreamer_transform_ProcessData,
545     Gstreamer_transform_ProcessEnd,
546     Gstreamer_YUV_QueryConnect,
547     Gstreamer_YUV_SetMediaType,
548     Gstreamer_YUV_ConnectInput,
549     Gstreamer_transform_Cleanup,
550     Gstreamer_transform_EndOfStream,
551     Gstreamer_transform_BeginFlush,
552     Gstreamer_transform_EndFlush,
553     Gstreamer_transform_NewSegment
554 };
555
556 IUnknown * CALLBACK Gstreamer_YUV_create(IUnknown *punkout, HRESULT *phr)
557 {
558     IUnknown *obj = NULL;
559     if (!Gstreamer_init())
560     {
561         *phr = E_FAIL;
562         return NULL;
563     }
564     *phr = Gstreamer_transform_create(punkout, &CLSID_Gstreamer_YUV, "ffmpegcolorspace", &Gstreamer_YUV_vtbl, (LPVOID*)&obj);
565     return obj;
566 }
567
568 HRESULT WINAPI GSTTf_QueryInterface(IBaseFilter * iface, REFIID riid, LPVOID * ppv)
569 {
570     HRESULT hr;
571     GstTfImpl *This = (GstTfImpl*)iface;
572     TRACE("(%p/%p)->(%s, %p)\n", This, iface, debugstr_guid(riid), ppv);
573
574     if (IsEqualIID(riid, &IID_IMediaSeeking))
575         return IUnknown_QueryInterface(This->seekthru_unk, riid, ppv);
576
577     hr = TransformFilterImpl_QueryInterface(iface, riid, ppv);
578
579     return hr;
580 }
581
582 static const IBaseFilterVtbl GSTTf_Vtbl =
583 {
584     GSTTf_QueryInterface,
585     BaseFilterImpl_AddRef,
586     TransformFilterImpl_Release,
587     BaseFilterImpl_GetClassID,
588     TransformFilterImpl_Stop,
589     TransformFilterImpl_Pause,
590     TransformFilterImpl_Run,
591     BaseFilterImpl_GetState,
592     BaseFilterImpl_SetSyncSource,
593     BaseFilterImpl_GetSyncSource,
594     BaseFilterImpl_EnumPins,
595     TransformFilterImpl_FindPin,
596     BaseFilterImpl_QueryFilterInfo,
597     BaseFilterImpl_JoinFilterGraph,
598     BaseFilterImpl_QueryVendorInfo
599 };