cmd: Add a space at the end of the first echo'ed batch line.
[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 DEFINE_GUID(WMMEDIASUBTYPE_MP3, 0x00000055, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
51
52 WINE_DEFAULT_DEBUG_CHANNEL(gstreamer);
53
54 struct typeinfo {
55     GstCaps *caps;
56     const char *type;
57 };
58
59 static const IBaseFilterVtbl GSTTf_Vtbl;
60
61 static gboolean match_element(GstPluginFeature *feature, gpointer gdata) {
62     struct typeinfo *data = (struct typeinfo*)gdata;
63     GstElementFactory *factory;
64     const GList *list;
65
66     if (!GST_IS_ELEMENT_FACTORY(feature))
67         return FALSE;
68     factory = GST_ELEMENT_FACTORY(feature);
69     if (!strstr(gst_element_factory_get_klass(factory), data->type))
70         return FALSE;
71     for (list = gst_element_factory_get_static_pad_templates(factory); list; list = list->next) {
72         GstStaticPadTemplate *pad = (GstStaticPadTemplate*)list->data;
73         GstCaps *caps;
74         gboolean ret;
75         if (pad->direction != GST_PAD_SINK)
76             continue;
77         caps = gst_static_caps_get(&pad->static_caps);
78         ret = gst_caps_is_always_compatible(caps, data->caps);
79         gst_caps_unref(caps);
80         if (ret)
81             return TRUE;
82     }
83     return FALSE;
84 }
85
86 static const char *Gstreamer_FindMatch(const char *strcaps)
87 {
88     struct typeinfo data;
89     GList *list, *copy;
90     guint bestrank = 0;
91     GstElementFactory *bestfactory = NULL;
92     GstCaps *caps = gst_caps_from_string(strcaps);
93
94     data.caps = caps;
95     data.type = "Decoder";
96     copy = gst_default_registry_feature_filter(match_element, 0, &data);
97     for (list = copy; list; list = list->next) {
98         GstElementFactory *factory = (GstElementFactory*)list->data;
99         guint rank;
100         rank = gst_plugin_feature_get_rank(GST_PLUGIN_FEATURE(factory));
101         if (rank > bestrank || !bestrank) {
102             bestrank = rank;
103             bestfactory = factory;
104         }
105     }
106     gst_caps_unref(caps);
107     g_list_free(copy);
108
109     if (!bestfactory) {
110         FIXME("Could not find plugin for %s\n", strcaps);
111         return NULL;
112     }
113     return gst_plugin_feature_get_name(GST_PLUGIN_FEATURE(bestfactory));
114 }
115
116 typedef struct GstTfImpl {
117     TransformFilter tf;
118     IUnknown *seekthru_unk;
119     const char *gstreamer_name;
120     GstElement *filter;
121     GstPad *my_src, *my_sink, *their_src, *their_sink;
122     LONG cbBuffer;
123 } GstTfImpl;
124
125 static HRESULT WINAPI Gstreamer_transform_ProcessBegin(TransformFilter *iface) {
126     GstTfImpl *This = (GstTfImpl*)iface;
127     int ret;
128
129     ret = gst_element_set_state(This->filter, GST_STATE_PLAYING);
130     TRACE("Returned: %i\n", ret);
131     return S_OK;
132 }
133
134 static HRESULT WINAPI Gstreamer_transform_DecideBufferSize(TransformFilter *tf, IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *ppropInputRequest)
135 {
136     GstTfImpl *This = (GstTfImpl*)tf;
137     ALLOCATOR_PROPERTIES actual;
138
139     if (!ppropInputRequest->cbAlign)
140         ppropInputRequest->cbAlign = 1;
141
142     ppropInputRequest->cbBuffer = This->cbBuffer;
143
144     if (!ppropInputRequest->cBuffers)
145         ppropInputRequest->cBuffers = 1;
146
147     return IMemAllocator_SetProperties(pAlloc, ppropInputRequest, &actual);
148 }
149
150 static void release_sample(void *data) {
151     TRACE("Releasing %p\n", data);
152     IMediaSample_Release((IMediaSample *)data);
153 }
154
155 static GstFlowReturn got_data(GstPad *pad, GstBuffer *buf) {
156     GstTfImpl *This = gst_pad_get_element_private(pad);
157     IMediaSample *sample = GST_APP_BUFFER(buf)->priv;
158     REFERENCE_TIME tStart, tStop;
159     HRESULT hr;
160
161     if (GST_BUFFER_TIMESTAMP_IS_VALID(buf) &&
162         GST_BUFFER_DURATION_IS_VALID(buf)) {
163         tStart = buf->timestamp / 100;
164         tStop = tStart + buf->duration / 100;
165         IMediaSample_SetTime(sample, &tStart, &tStop);
166     }
167     else
168         IMediaSample_SetTime(sample, NULL, NULL);
169     if (GST_BUFFER_OFFSET_IS_VALID(buf) &&
170         GST_BUFFER_OFFSET_END_IS_VALID(buf)) {
171         tStart = buf->offset / 100;
172         tStop = buf->offset_end / 100;
173         IMediaSample_SetMediaTime(sample, &tStart, &tStop);
174     }
175     else
176         IMediaSample_SetMediaTime(sample, NULL, NULL);
177
178     IMediaSample_SetDiscontinuity(sample, GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_DISCONT));
179     IMediaSample_SetPreroll(sample, GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_PREROLL));
180     IMediaSample_SetSyncPoint(sample, !GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_DELTA_UNIT));
181
182     hr = BaseOutputPinImpl_Deliver((BaseOutputPin*)This->tf.ppPins[1], sample);
183     gst_buffer_unref(buf);
184     if (FAILED(hr))
185         return GST_FLOW_WRONG_STATE;
186     if (hr != S_OK)
187         return GST_FLOW_RESEND;
188     return GST_FLOW_OK;
189 }
190
191 static GstFlowReturn request_buffer(GstPad *pad, guint64 ofs, guint size, GstCaps *caps, GstBuffer **buf) {
192     GstTfImpl *This = gst_pad_get_element_private(pad);
193     IMediaSample *sample;
194     BYTE *ptr;
195     HRESULT hr;
196     TRACE("Requesting buffer\n");
197
198     hr = BaseOutputPinImpl_GetDeliveryBuffer((BaseOutputPin*)This->tf.ppPins[1], &sample, NULL, NULL, 0);
199     if (FAILED(hr)) {
200         ERR("Could not get output buffer: %08x\n", hr);
201         return GST_FLOW_WRONG_STATE;
202     }
203     IMediaSample_SetActualDataLength(sample, size);
204     IMediaSample_GetPointer(sample, &ptr);
205     *buf = gst_app_buffer_new(ptr, size, release_sample, sample);
206
207     if (!*buf) {
208         IMediaSample_Release(sample);
209         ERR("Out of memory\n");
210         return GST_FLOW_ERROR;
211     }
212     if (!caps)
213         caps = gst_pad_get_caps_reffed(This->my_sink);
214     gst_buffer_set_caps(*buf, caps);
215     return GST_FLOW_OK;
216 }
217
218 static HRESULT WINAPI Gstreamer_transform_ProcessData(TransformFilter *iface, IMediaSample *sample) {
219     GstTfImpl *This = (GstTfImpl*)iface;
220     REFERENCE_TIME tStart, tStop;
221     BYTE *data;
222     GstBuffer *buf;
223     HRESULT hr;
224     int ret;
225     TRACE("Reading %p\n", sample);
226
227     EnterCriticalSection(&This->tf.filter.csFilter);
228     IMediaSample_GetPointer(sample, &data);
229     buf = gst_app_buffer_new(data, IMediaSample_GetActualDataLength(sample), release_sample, sample);
230     if (!buf) {
231         LeaveCriticalSection(&This->tf.filter.csFilter);
232         return S_OK;
233     }
234     gst_buffer_set_caps(buf, gst_pad_get_caps_reffed(This->my_src));
235     IMediaSample_AddRef(sample);
236     buf->duration = buf->timestamp = -1;
237     hr = IMediaSample_GetTime(sample, &tStart, &tStop);
238     if (SUCCEEDED(hr)) {
239         buf->timestamp = tStart * 100;
240         if (hr == S_OK)
241             buf->duration = (tStop - tStart)*100;
242     }
243     if (IMediaSample_GetMediaTime(sample, &tStart, &tStop) == S_OK) {
244         buf->offset = tStart * 100;
245         buf->offset_end = tStop * 100;
246     }
247     if (IMediaSample_IsDiscontinuity(sample) == S_OK)
248         GST_BUFFER_FLAG_SET(buf, GST_BUFFER_FLAG_DISCONT);
249     if (IMediaSample_IsPreroll(sample) == S_OK)
250         GST_BUFFER_FLAG_SET(buf, GST_BUFFER_FLAG_PREROLL);
251     if (IMediaSample_IsSyncPoint(sample) != S_OK)
252         GST_BUFFER_FLAG_SET(buf, GST_BUFFER_FLAG_DELTA_UNIT);
253     LeaveCriticalSection(&This->tf.filter.csFilter);
254     ret = gst_pad_push(This->my_src, buf);
255     if (ret)
256         WARN("Sending returned: %i\n", ret);
257     if (ret == GST_FLOW_ERROR)
258         return E_FAIL;
259     if (ret == GST_FLOW_WRONG_STATE)
260         return VFW_E_WRONG_STATE;
261     if (ret == GST_FLOW_RESEND)
262         return S_FALSE;
263     return S_OK;
264 }
265
266 static HRESULT WINAPI Gstreamer_transform_ProcessEnd(TransformFilter *iface) {
267     GstTfImpl *This = (GstTfImpl*)iface;
268     int ret;
269
270     ret = gst_element_set_state(This->filter, GST_STATE_PAUSED);
271     TRACE("Returned: %i\n", ret);
272     return S_OK;
273 }
274
275 static void Gstreamer_transform_pad_added(GstElement *filter, GstPad *pad, GstTfImpl *This)
276 {
277     int ret;
278     if (!GST_PAD_IS_SRC(pad))
279         return;
280
281     ret = gst_pad_link(pad, This->my_sink);
282     if (ret < 0)
283         WARN("Failed to link with %i\n", ret);
284     This->their_src = pad;
285
286     gst_pad_set_active(pad, TRUE);
287     gst_pad_set_active(This->my_sink, TRUE);
288 }
289
290 static HRESULT Gstreamer_transform_ConnectInput(GstTfImpl *This, const AM_MEDIA_TYPE *amt, GstCaps *capsin, GstCaps *capsout) {
291     GstIterator *it;
292     int done = 0, found = 0, ret;
293
294     This->filter = gst_element_factory_make(This->gstreamer_name, NULL);
295     if (!This->filter) {
296         FIXME("Could not make %s filter\n", This->gstreamer_name);
297         return E_FAIL;
298     }
299     This->my_src = gst_pad_new(NULL, GST_PAD_SRC);
300     gst_pad_set_element_private (This->my_src, This);
301
302     This->my_sink = gst_pad_new(NULL, GST_PAD_SINK);
303     gst_pad_set_chain_function(This->my_sink, got_data);
304     gst_pad_set_bufferalloc_function(This->my_sink, request_buffer);
305     gst_pad_set_element_private (This->my_sink, This);
306
307     ret = gst_pad_set_caps(This->my_src, capsin);
308     if (ret < 0) {
309         WARN("Failed to set caps on own source with %i\n", ret);
310         return E_FAIL;
311     }
312
313     ret = gst_pad_set_caps(This->my_sink, capsout);
314     if (ret < 0) {
315         WARN("Failed to set caps on own sink with %i\n", ret);
316         return E_FAIL;
317     }
318
319     it = gst_element_iterate_sink_pads(This->filter);
320     while (!done) {
321         gpointer item;
322
323         switch (gst_iterator_next(it, &item)) {
324         case GST_ITERATOR_RESYNC:
325             gst_iterator_resync (it);
326             break;
327         case GST_ITERATOR_OK:
328             This->their_sink = item;
329         case GST_ITERATOR_ERROR:
330         case GST_ITERATOR_DONE:
331             done = 1;
332             break;
333         }
334     }
335     gst_iterator_free(it);
336     if (!This->their_sink) {
337         ERR("Could not find sink on filter %s\n", This->gstreamer_name);
338         return E_FAIL;
339     }
340
341     it = gst_element_iterate_src_pads(This->filter);
342     gst_iterator_resync(it);
343     done = 0;
344     while (!done) {
345         gpointer item;
346
347         switch (gst_iterator_next(it, &item)) {
348         case GST_ITERATOR_RESYNC:
349             gst_iterator_resync (it);
350             break;
351         case GST_ITERATOR_OK:
352             This->their_src = item;
353         case GST_ITERATOR_ERROR:
354         case GST_ITERATOR_DONE:
355             done = 1;
356             break;
357         }
358     }
359     gst_iterator_free(it);
360     found = !!This->their_src;
361     if (!found)
362         g_signal_connect(This->filter, "pad-added", G_CALLBACK(Gstreamer_transform_pad_added), This);
363     ret = gst_pad_link(This->my_src, This->their_sink);
364     if (ret < 0) {
365         WARN("Failed to link with %i\n", ret);
366         return E_FAIL;
367     }
368
369     if (found)
370         Gstreamer_transform_pad_added(This->filter, This->their_src, This);
371
372     if (!gst_pad_is_linked(This->my_sink))
373         return E_FAIL;
374
375     TRACE("Connected\n");
376     return S_OK;
377 }
378
379 static HRESULT WINAPI Gstreamer_transform_Cleanup(TransformFilter *tf, PIN_DIRECTION dir) {
380     GstTfImpl *This = (GstTfImpl*)tf;
381
382     if (dir == PINDIR_INPUT)
383     {
384         if (This->filter) {
385             gst_element_set_state(This->filter, GST_STATE_NULL);
386             gst_object_unref(This->filter);
387         }
388         This->filter = NULL;
389         if (This->my_src) {
390             gst_pad_unlink(This->my_src, This->their_sink);
391             gst_object_unref(This->my_src);
392         }
393         if (This->my_sink) {
394             gst_pad_unlink(This->their_src, This->my_sink);
395             gst_object_unref(This->my_sink);
396         }
397         This->my_sink = This->my_src = This->their_sink = This->their_src = NULL;
398         FIXME("%p stub\n", This);
399     }
400     return S_OK;
401 }
402
403 static HRESULT WINAPI Gstreamer_transform_EndOfStream(TransformFilter *iface) {
404     GstTfImpl *This = (GstTfImpl*)iface;
405     TRACE("%p\n", This);
406
407     gst_pad_push_event(This->my_src, gst_event_new_eos());
408     return S_OK;
409 }
410
411 static HRESULT WINAPI Gstreamer_transform_BeginFlush(TransformFilter *iface) {
412     GstTfImpl *This = (GstTfImpl*)iface;
413     TRACE("%p\n", This);
414
415     gst_pad_push_event(This->my_src, gst_event_new_flush_start());
416     return S_OK;
417 }
418
419 static HRESULT WINAPI Gstreamer_transform_EndFlush(TransformFilter *iface) {
420     GstTfImpl *This = (GstTfImpl*)iface;
421     TRACE("%p\n", This);
422
423     gst_pad_push_event(This->my_src, gst_event_new_flush_stop());
424     return S_OK;
425 }
426
427 static HRESULT WINAPI Gstreamer_transform_NewSegment(TransformFilter *iface, REFERENCE_TIME tStart, REFERENCE_TIME tStop, double dRate) {
428     GstTfImpl *This = (GstTfImpl*)iface;
429     TRACE("%p\n", This);
430
431     gst_pad_push_event(This->my_src, gst_event_new_new_segment_full(1,
432                        1.0, dRate, GST_FORMAT_TIME, tStart*100, tStop <= tStart ? -1 : tStop * 100, tStart*100));
433     return S_OK;
434 }
435
436 static HRESULT Gstreamer_transform_create(IUnknown *punkout, const CLSID *clsid, const char *name, const TransformFilterFuncTable *vtbl, void **obj)
437 {
438     GstTfImpl *This;
439
440     if (FAILED(TransformFilter_Construct(&GSTTf_Vtbl, sizeof(GstTfImpl), clsid, vtbl, (IBaseFilter**)&This)))
441         return E_OUTOFMEMORY;
442     else
443     {
444         ISeekingPassThru *passthru;
445         CoCreateInstance(&CLSID_SeekingPassThru, (IUnknown*)This, CLSCTX_INPROC_SERVER, &IID_IUnknown, (void**)&This->seekthru_unk);
446         IUnknown_QueryInterface(This->seekthru_unk, &IID_ISeekingPassThru, (void**)&passthru);
447         ISeekingPassThru_Init(passthru, FALSE, (IPin*)This->tf.ppPins[0]);
448         ISeekingPassThru_Release(passthru);
449     }
450
451     This->gstreamer_name = name;
452     *obj = This;
453
454     return S_OK;
455 }
456
457 static HRESULT WINAPI Gstreamer_Mp3_QueryConnect(TransformFilter *iface, const AM_MEDIA_TYPE *amt) {
458     GstTfImpl *This = (GstTfImpl*)iface;
459     TRACE("%p %p\n", This, amt);
460     dump_AM_MEDIA_TYPE(amt);
461
462     if ( (!IsEqualGUID(&amt->majortype, &MEDIATYPE_Audio) &&
463           !IsEqualGUID(&amt->majortype, &MEDIATYPE_Stream)) ||
464          (!IsEqualGUID(&amt->subtype, &MEDIASUBTYPE_MPEG1AudioPayload) &&
465           !IsEqualGUID(&amt->subtype, &WMMEDIASUBTYPE_MP3))
466         || !IsEqualGUID(&amt->formattype, &FORMAT_WaveFormatEx))
467         return S_FALSE;
468
469     return S_OK;
470 }
471
472 static HRESULT WINAPI Gstreamer_Mp3_SetMediaType(TransformFilter *tf, PIN_DIRECTION dir, const AM_MEDIA_TYPE *amt) {
473     GstTfImpl *This = (GstTfImpl*)tf;
474     GstCaps *capsin, *capsout;
475     AM_MEDIA_TYPE *outpmt = &This->tf.pmt;
476     WAVEFORMATEX *wfx, *wfxin;
477     HRESULT hr;
478     int layer;
479
480     if (dir != PINDIR_INPUT)
481         return S_OK;
482
483     if (Gstreamer_Mp3_QueryConnect(&This->tf, amt) == S_FALSE || !amt->pbFormat)
484         return VFW_E_TYPE_NOT_ACCEPTED;
485
486     wfxin = (WAVEFORMATEX*)amt->pbFormat;
487     switch (wfxin->wFormatTag) {
488         case WAVE_FORMAT_MPEGLAYER3:
489             layer = 3;
490             break;
491         case WAVE_FORMAT_MPEG: {
492             MPEG1WAVEFORMAT *mpgformat = (MPEG1WAVEFORMAT*)wfxin;
493             layer = mpgformat->fwHeadLayer;
494             break;
495         }
496         default:
497             FIXME("Unhandled tag %x\n", wfxin->wFormatTag);
498             return E_FAIL;
499     }
500
501     FreeMediaType(outpmt);
502     CopyMediaType(outpmt, amt);
503
504     outpmt->subtype = MEDIASUBTYPE_PCM;
505     outpmt->formattype = FORMAT_WaveFormatEx;
506     outpmt->cbFormat = sizeof(*wfx);
507     CoTaskMemFree(outpmt->pbFormat);
508     wfx = CoTaskMemAlloc(outpmt->cbFormat);
509     outpmt->pbFormat = (BYTE*)wfx;
510     wfx->wFormatTag = WAVE_FORMAT_PCM;
511     wfx->wBitsPerSample = 16;
512     wfx->nSamplesPerSec = wfxin->nSamplesPerSec;
513     wfx->nChannels = wfxin->nChannels;
514     wfx->nBlockAlign = wfx->wBitsPerSample * wfx->nChannels / 8;
515     wfx->cbSize = 0;
516     wfx->nAvgBytesPerSec = wfx->nSamplesPerSec * wfx->nBlockAlign;
517
518     capsin = gst_caps_new_simple("audio/mpeg",
519                                  "mpegversion", G_TYPE_INT, 1,
520                                  "layer", G_TYPE_INT, layer,
521                                  "rate", G_TYPE_INT, wfx->nSamplesPerSec,
522                                  "channels", G_TYPE_INT, wfx->nChannels,
523                                  NULL);
524     capsout = gst_caps_new_simple("audio/x-raw-int",
525                                   "endianness", G_TYPE_INT, 1234,
526                                   "signed", G_TYPE_BOOLEAN, 1,
527                                   "width", G_TYPE_INT, 16,
528                                   "depth", G_TYPE_INT, 16,
529                                   "rate", G_TYPE_INT, wfx->nSamplesPerSec,
530                                   "channels", G_TYPE_INT, wfx->nChannels,
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 = wfx->nAvgBytesPerSec / 4;
538
539     return hr;
540 }
541
542 static HRESULT WINAPI Gstreamer_Mp3_ConnectInput(TransformFilter *tf, PIN_DIRECTION dir, IPin *pin)
543 {
544     return S_OK;
545 }
546
547 static const TransformFilterFuncTable Gstreamer_Mp3_vtbl = {
548     Gstreamer_transform_DecideBufferSize,
549     Gstreamer_transform_ProcessBegin,
550     Gstreamer_transform_ProcessData,
551     Gstreamer_transform_ProcessEnd,
552     Gstreamer_Mp3_QueryConnect,
553     Gstreamer_Mp3_SetMediaType,
554     Gstreamer_Mp3_ConnectInput,
555     Gstreamer_transform_Cleanup,
556     Gstreamer_transform_EndOfStream,
557     Gstreamer_transform_BeginFlush,
558     Gstreamer_transform_EndFlush,
559     Gstreamer_transform_NewSegment
560 };
561
562 IUnknown * CALLBACK Gstreamer_Mp3_create(IUnknown *punkout, HRESULT *phr)
563 {
564     const char *plugin;
565     IUnknown *obj = NULL;
566     if (!Gstreamer_init())
567     {
568         *phr = E_FAIL;
569         return NULL;
570     }
571     plugin = Gstreamer_FindMatch("audio/mpeg, mpegversion=(int) 1");
572     if (!plugin)
573     {
574         *phr = E_FAIL;
575         return NULL;
576     }
577     *phr = Gstreamer_transform_create(punkout, &CLSID_Gstreamer_Mp3, plugin, &Gstreamer_Mp3_vtbl, (LPVOID*)&obj);
578     return obj;
579 }
580
581 static HRESULT WINAPI Gstreamer_YUV_QueryConnect(TransformFilter *iface, const AM_MEDIA_TYPE *amt) {
582     GstTfImpl *This = (GstTfImpl*)iface;
583     TRACE("%p %p\n", This, amt);
584     dump_AM_MEDIA_TYPE(amt);
585
586     if (!IsEqualGUID(&amt->majortype, &MEDIATYPE_Video) ||
587         (!IsEqualGUID(&amt->formattype, &FORMAT_VideoInfo) &&
588          !IsEqualGUID(&amt->formattype, &FORMAT_VideoInfo2)))
589         return S_FALSE;
590     if (memcmp(&amt->subtype.Data2, &MEDIATYPE_Video.Data2, sizeof(GUID) - sizeof(amt->subtype.Data1)))
591         return S_FALSE;
592     switch (amt->subtype.Data1) {
593         case mmioFOURCC('I','4','2','0'):
594         case mmioFOURCC('Y','V','1','2'):
595         case mmioFOURCC('N','V','1','2'):
596         case mmioFOURCC('N','V','2','1'):
597         case mmioFOURCC('Y','U','Y','2'):
598         case mmioFOURCC('Y','V','Y','U'):
599             return S_OK;
600         default:
601             WARN("Unhandled fourcc %s\n", debugstr_an((char*)&amt->subtype.Data1, 4));
602             return S_FALSE;
603     }
604 }
605
606 static HRESULT WINAPI Gstreamer_YUV_ConnectInput(TransformFilter *tf, PIN_DIRECTION dir, IPin *pin)
607 {
608     return S_OK;
609 }
610
611 static HRESULT WINAPI Gstreamer_YUV_SetMediaType(TransformFilter *tf, PIN_DIRECTION dir,  const AM_MEDIA_TYPE *amt) {
612     GstTfImpl *This = (GstTfImpl*)tf;
613     GstCaps *capsin, *capsout;
614     AM_MEDIA_TYPE *outpmt = &This->tf.pmt;
615     HRESULT hr;
616     int avgtime;
617     DWORD width, height;
618
619     if (dir != PINDIR_INPUT)
620         return S_OK;
621
622     if (Gstreamer_YUV_QueryConnect(&This->tf, amt) == S_FALSE || !amt->pbFormat)
623         return E_FAIL;
624
625     FreeMediaType(outpmt);
626     CopyMediaType(outpmt, amt);
627
628     if (IsEqualGUID(&amt->formattype, &FORMAT_VideoInfo)) {
629         VIDEOINFOHEADER *vih = (VIDEOINFOHEADER*)outpmt->pbFormat;
630         avgtime = vih->AvgTimePerFrame;
631         width = vih->bmiHeader.biWidth;
632         height = vih->bmiHeader.biHeight;
633         if ((LONG)vih->bmiHeader.biHeight > 0)
634             vih->bmiHeader.biHeight = -vih->bmiHeader.biHeight;
635         vih->bmiHeader.biBitCount = 24;
636         vih->bmiHeader.biCompression = BI_RGB;
637     } else {
638         VIDEOINFOHEADER2 *vih = (VIDEOINFOHEADER2*)outpmt->pbFormat;
639         avgtime = vih->AvgTimePerFrame;
640         width = vih->bmiHeader.biWidth;
641         height = vih->bmiHeader.biHeight;
642         if ((LONG)vih->bmiHeader.biHeight > 0)
643             vih->bmiHeader.biHeight = -vih->bmiHeader.biHeight;
644         vih->bmiHeader.biBitCount = 24;
645         vih->bmiHeader.biCompression = BI_RGB;
646     }
647     if (!avgtime)
648         avgtime = 10000000 / 30;
649
650     outpmt->subtype = MEDIASUBTYPE_RGB24;
651
652     capsin = gst_caps_new_simple("video/x-raw-yuv",
653                                  "format", GST_TYPE_FOURCC, amt->subtype.Data1,
654                                  "width", G_TYPE_INT, width,
655                                  "height", G_TYPE_INT, height,
656                                  "framerate", GST_TYPE_FRACTION, 10000000, avgtime,
657                                  NULL);
658     capsout = gst_caps_new_simple("video/x-raw-rgb",
659                                   "endianness", G_TYPE_INT, 4321,
660                                   "width", G_TYPE_INT, width,
661                                   "height", G_TYPE_INT, height,
662                                   "framerate", GST_TYPE_FRACTION, 10000000, avgtime,
663                                   "bpp", G_TYPE_INT, 24,
664                                   "depth", G_TYPE_INT, 24,
665                                   "red_mask", G_TYPE_INT, 0xff,
666                                   "green_mask", G_TYPE_INT, 0xff00,
667                                   "blue_mask", G_TYPE_INT, 0xff0000,
668                                    NULL);
669
670     hr = Gstreamer_transform_ConnectInput(This, amt, capsin, capsout);
671     gst_caps_unref(capsin);
672     gst_caps_unref(capsout);
673
674     This->cbBuffer = width * height * 4;
675     return hr;
676 }
677
678 static const TransformFilterFuncTable Gstreamer_YUV_vtbl = {
679     Gstreamer_transform_DecideBufferSize,
680     Gstreamer_transform_ProcessBegin,
681     Gstreamer_transform_ProcessData,
682     Gstreamer_transform_ProcessEnd,
683     Gstreamer_YUV_QueryConnect,
684     Gstreamer_YUV_SetMediaType,
685     Gstreamer_YUV_ConnectInput,
686     Gstreamer_transform_Cleanup,
687     Gstreamer_transform_EndOfStream,
688     Gstreamer_transform_BeginFlush,
689     Gstreamer_transform_EndFlush,
690     Gstreamer_transform_NewSegment
691 };
692
693 IUnknown * CALLBACK Gstreamer_YUV_create(IUnknown *punkout, HRESULT *phr)
694 {
695     IUnknown *obj = NULL;
696     if (!Gstreamer_init())
697     {
698         *phr = E_FAIL;
699         return NULL;
700     }
701     *phr = Gstreamer_transform_create(punkout, &CLSID_Gstreamer_YUV, "ffmpegcolorspace", &Gstreamer_YUV_vtbl, (LPVOID*)&obj);
702     return obj;
703 }
704
705 static HRESULT WINAPI Gstreamer_AudioConvert_QueryConnect(TransformFilter *iface, const AM_MEDIA_TYPE *amt) {
706     GstTfImpl *This = (GstTfImpl*)iface;
707     TRACE("%p %p\n", This, amt);
708     dump_AM_MEDIA_TYPE(amt);
709
710     if (!IsEqualGUID(&amt->majortype, &MEDIATYPE_Audio) ||
711         !IsEqualGUID(&amt->subtype, &MEDIASUBTYPE_PCM) ||
712         !IsEqualGUID(&amt->formattype, &FORMAT_WaveFormatEx))
713         return S_FALSE;
714     return S_OK;
715 }
716
717 static HRESULT WINAPI Gstreamer_AudioConvert_ConnectInput(TransformFilter *tf, PIN_DIRECTION dir, IPin *pin)
718 {
719     return S_OK;
720 }
721
722 static HRESULT WINAPI Gstreamer_AudioConvert_SetMediaType(TransformFilter *tf, PIN_DIRECTION dir, const AM_MEDIA_TYPE *amt) {
723     GstTfImpl *This = (GstTfImpl*)tf;
724     GstCaps *capsin, *capsout;
725     AM_MEDIA_TYPE *outpmt = &This->tf.pmt;
726     WAVEFORMATEX *inwfe;
727     WAVEFORMATEX *outwfe;
728     WAVEFORMATEXTENSIBLE *outwfx;
729     HRESULT hr;
730     int inisfloat = 0, indepth;
731
732     if (dir != PINDIR_INPUT)
733         return S_OK;
734
735     if (Gstreamer_AudioConvert_QueryConnect(&This->tf, amt) == S_FALSE || !amt->pbFormat)
736         return E_FAIL;
737
738     FreeMediaType(outpmt);
739     *outpmt = *amt;
740     outpmt->pUnk = NULL;
741     outpmt->cbFormat = sizeof(WAVEFORMATEXTENSIBLE);
742     outpmt->pbFormat = CoTaskMemAlloc(outpmt->cbFormat);
743
744     inwfe = (WAVEFORMATEX*)amt->pbFormat;
745     indepth = inwfe->wBitsPerSample;
746     if (inwfe->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
747         WAVEFORMATEXTENSIBLE *inwfx = (WAVEFORMATEXTENSIBLE*)inwfe;
748         inisfloat = IsEqualGUID(&inwfx->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT);
749         if (inwfx->Samples.wValidBitsPerSample)
750             indepth = inwfx->Samples.wValidBitsPerSample;
751     }
752
753     capsin = gst_caps_new_simple(inisfloat ? "audio/x-raw-float" : "audio/x-raw-int",
754                                  "endianness", G_TYPE_INT, 1234,
755                                  "width", G_TYPE_INT, inwfe->wBitsPerSample,
756                                  "depth", G_TYPE_INT, indepth,
757                                  "channels", G_TYPE_INT, inwfe->nChannels,
758                                  "rate", G_TYPE_INT, inwfe->nSamplesPerSec,
759                                   NULL);
760
761     outwfe = (WAVEFORMATEX*)outpmt->pbFormat;
762     outwfx = (WAVEFORMATEXTENSIBLE*)outwfe;
763     outwfe->wFormatTag = WAVE_FORMAT_EXTENSIBLE;
764     outwfe->nChannels = 2;
765     outwfe->nSamplesPerSec = inwfe->nSamplesPerSec;
766     outwfe->wBitsPerSample = 16;
767     outwfe->nBlockAlign = outwfe->nChannels * outwfe->wBitsPerSample / 8;
768     outwfe->nAvgBytesPerSec = outwfe->nBlockAlign * outwfe->nSamplesPerSec;
769     outwfe->cbSize = sizeof(*outwfx) - sizeof(*outwfe);
770     outwfx->Samples.wValidBitsPerSample = outwfe->wBitsPerSample;
771     outwfx->dwChannelMask = SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT;
772     outwfx->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
773
774     capsout = gst_caps_new_simple("audio/x-raw-int",
775                                   "endianness", G_TYPE_INT, 1234,
776                                   "width", G_TYPE_INT, outwfe->wBitsPerSample,
777                                   "depth", G_TYPE_INT, outwfx->Samples.wValidBitsPerSample,
778                                   "channels", G_TYPE_INT, outwfe->nChannels,
779                                   "rate", G_TYPE_INT, outwfe->nSamplesPerSec,
780                                    NULL);
781
782     hr = Gstreamer_transform_ConnectInput(This, amt, capsin, capsout);
783     FIXME("%08x\n", hr);
784     gst_caps_unref(capsin);
785     gst_caps_unref(capsout);
786
787     This->cbBuffer = inwfe->nAvgBytesPerSec;
788     return hr;
789 }
790
791 static const TransformFilterFuncTable Gstreamer_AudioConvert_vtbl = {
792     Gstreamer_transform_DecideBufferSize,
793     Gstreamer_transform_ProcessBegin,
794     Gstreamer_transform_ProcessData,
795     Gstreamer_transform_ProcessEnd,
796     Gstreamer_AudioConvert_QueryConnect,
797     Gstreamer_AudioConvert_SetMediaType,
798     Gstreamer_AudioConvert_ConnectInput,
799     Gstreamer_transform_Cleanup,
800     Gstreamer_transform_EndOfStream,
801     Gstreamer_transform_BeginFlush,
802     Gstreamer_transform_EndFlush,
803     Gstreamer_transform_NewSegment
804 };
805
806 IUnknown * CALLBACK Gstreamer_AudioConvert_create(IUnknown *punkout, HRESULT *phr)
807 {
808     IUnknown *obj = NULL;
809     if (!Gstreamer_init())
810     {
811         *phr = E_FAIL;
812         return NULL;
813     }
814     *phr = Gstreamer_transform_create(punkout, &CLSID_Gstreamer_AudioConvert, "audioconvert", &Gstreamer_AudioConvert_vtbl, (LPVOID*)&obj);
815     return obj;
816 }
817
818 HRESULT WINAPI GSTTf_QueryInterface(IBaseFilter * iface, REFIID riid, LPVOID * ppv)
819 {
820     HRESULT hr;
821     GstTfImpl *This = (GstTfImpl*)iface;
822     TRACE("(%p/%p)->(%s, %p)\n", This, iface, debugstr_guid(riid), ppv);
823
824     if (IsEqualIID(riid, &IID_IMediaSeeking))
825         return IUnknown_QueryInterface(This->seekthru_unk, riid, ppv);
826
827     hr = TransformFilterImpl_QueryInterface(iface, riid, ppv);
828
829     return hr;
830 }
831
832 static const IBaseFilterVtbl GSTTf_Vtbl =
833 {
834     GSTTf_QueryInterface,
835     BaseFilterImpl_AddRef,
836     TransformFilterImpl_Release,
837     BaseFilterImpl_GetClassID,
838     TransformFilterImpl_Stop,
839     TransformFilterImpl_Pause,
840     TransformFilterImpl_Run,
841     BaseFilterImpl_GetState,
842     BaseFilterImpl_SetSyncSource,
843     BaseFilterImpl_GetSyncSource,
844     BaseFilterImpl_EnumPins,
845     TransformFilterImpl_FindPin,
846     BaseFilterImpl_QueryFilterInfo,
847     BaseFilterImpl_JoinFilterGraph,
848     BaseFilterImpl_QueryVendorInfo
849 };