winegstreamer: Add gstreamer YUV->RGB transform filter.
[wine/wine-gecko.git] / dlls / winegstreamer / gsttffilter.c
blob7a13e6f824db778c1082c8317a31a46ad7ebf3fe
1 /*
2 * GStreamer wrapper filter
4 * Copyright 2010 Maarten Lankhorst for CodeWeavers
5 * Copyright 2010 Aric Stewart for CodeWeavers
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.
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.
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
23 #include "config.h"
25 #include <gst/app/gstappsink.h>
26 #include <gst/app/gstappsrc.h>
27 #include <gst/app/gstappbuffer.h>
29 #include "gst_private.h"
30 #include "gst_guids.h"
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"
44 #include <assert.h>
46 #include "wine/unicode.h"
47 #include "wine/debug.h"
49 #include "initguid.h"
51 WINE_DEFAULT_DEBUG_CHANNEL(gstreamer);
53 struct typeinfo {
54 GstCaps *caps;
55 const char *type;
58 static const IBaseFilterVtbl GSTTf_Vtbl;
60 static gboolean match_element(GstPluginFeature *feature, gpointer gdata) {
61 struct typeinfo *data = (struct typeinfo*)gdata;
62 GstElementFactory *factory;
63 const GList *list;
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;
82 return FALSE;
85 static const char *Gstreamer_FindMatch(const char *strcaps)
87 struct typeinfo data;
88 GList *list, *copy;
89 guint bestrank = 0;
90 GstElementFactory *bestfactory = NULL;
91 GstCaps *caps = gst_caps_from_string(strcaps);
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;
105 gst_caps_unref(caps);
106 g_list_free(copy);
108 if (!bestfactory) {
109 FIXME("Could not find plugin for %s\n", strcaps);
110 return NULL;
112 return gst_plugin_feature_get_name(GST_PLUGIN_FEATURE(bestfactory));
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;
124 static HRESULT WINAPI Gstreamer_transform_ProcessBegin(TransformFilter *iface) {
125 GstTfImpl *This = (GstTfImpl*)iface;
126 int ret;
128 ret = gst_element_set_state(This->filter, GST_STATE_PLAYING);
129 TRACE("Returned: %i\n", ret);
130 return S_OK;
133 static HRESULT WINAPI Gstreamer_transform_DecideBufferSize(TransformFilter *tf, IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *ppropInputRequest)
135 GstTfImpl *This = (GstTfImpl*)tf;
136 ALLOCATOR_PROPERTIES actual;
138 if (!ppropInputRequest->cbAlign)
139 ppropInputRequest->cbAlign = 1;
141 ppropInputRequest->cbBuffer = This->cbBuffer;
143 if (!ppropInputRequest->cBuffers)
144 ppropInputRequest->cBuffers = 1;
146 return IMemAllocator_SetProperties(pAlloc, ppropInputRequest, &actual);
149 static void release_sample(void *data) {
150 TRACE("Releasing %p\n", data);
151 IMediaSample_Release((IMediaSample *)data);
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;
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);
166 else
167 IMediaSample_SetTime(sample, NULL, NULL);
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));
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;
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");
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;
194 IMediaSample_SetActualDataLength(sample, size);
195 IMediaSample_GetPointer(sample, &ptr);
196 *buf = gst_app_buffer_new(ptr, size, release_sample, sample);
198 if (!*buf) {
199 IMediaSample_Release(sample);
200 ERR("Out of memory\n");
201 return GST_FLOW_ERROR;
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;
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);
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;
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;
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;
253 static HRESULT WINAPI Gstreamer_transform_ProcessEnd(TransformFilter *iface) {
254 GstTfImpl *This = (GstTfImpl*)iface;
255 int ret;
257 ret = gst_element_set_state(This->filter, GST_STATE_PAUSED);
258 TRACE("Returned: %i\n", ret);
259 return S_OK;
262 static void Gstreamer_transform_pad_added(GstElement *filter, GstPad *pad, GstTfImpl *This)
264 int ret;
265 if (!GST_PAD_IS_SRC(pad))
266 return;
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;
273 gst_pad_set_active(pad, TRUE);
274 gst_pad_set_active(This->my_sink, TRUE);
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;
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;
286 This->my_src = gst_pad_new(NULL, GST_PAD_SRC);
287 gst_pad_set_element_private (This->my_src, This);
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);
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;
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;
306 it = gst_element_iterate_sink_pads(This->filter);
307 while (!done) {
308 gpointer item;
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;
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;
328 it = gst_element_iterate_src_pads(This->filter);
329 gst_iterator_resync(it);
330 done = 0;
331 while (!done) {
332 gpointer item;
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;
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;
356 if (found)
357 Gstreamer_transform_pad_added(This->filter, This->their_src, This);
359 if (!gst_pad_is_linked(This->my_sink))
360 return E_FAIL;
362 TRACE("Connected\n");
363 return S_OK;
366 static HRESULT WINAPI Gstreamer_transform_Cleanup(TransformFilter *tf, PIN_DIRECTION dir) {
367 GstTfImpl *This = (GstTfImpl*)tf;
369 if (dir == PINDIR_INPUT)
371 if (This->filter) {
372 gst_element_set_state(This->filter, GST_STATE_NULL);
373 gst_object_unref(This->filter);
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);
380 if (This->my_sink) {
381 gst_pad_unlink(This->their_src, This->my_sink);
382 gst_object_unref(This->my_sink);
384 This->my_sink = This->my_src = This->their_sink = This->their_src = NULL;
385 FIXME("%p stub\n", This);
387 return S_OK;
390 static HRESULT WINAPI Gstreamer_transform_EndOfStream(TransformFilter *iface) {
391 GstTfImpl *This = (GstTfImpl*)iface;
392 TRACE("%p\n", This);
394 gst_pad_push_event(This->my_src, gst_event_new_eos());
395 return S_OK;
398 static HRESULT WINAPI Gstreamer_transform_BeginFlush(TransformFilter *iface) {
399 GstTfImpl *This = (GstTfImpl*)iface;
400 TRACE("%p\n", This);
402 gst_pad_push_event(This->my_src, gst_event_new_flush_start());
403 return S_OK;
406 static HRESULT WINAPI Gstreamer_transform_EndFlush(TransformFilter *iface) {
407 GstTfImpl *This = (GstTfImpl*)iface;
408 TRACE("%p\n", This);
410 gst_pad_push_event(This->my_src, gst_event_new_flush_stop());
411 return S_OK;
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);
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;
423 static HRESULT Gstreamer_transform_create(IUnknown *punkout, const CLSID *clsid, const char *name, const TransformFilterFuncTable *vtbl, void **obj)
425 GstTfImpl *This;
427 if (FAILED(TransformFilter_Construct(&GSTTf_Vtbl, sizeof(GstTfImpl), clsid, vtbl, (IBaseFilter**)&This)))
428 return E_OUTOFMEMORY;
429 else
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);
438 This->gstreamer_name = name;
439 *obj = This;
441 return S_OK;
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);
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;
469 static HRESULT WINAPI Gstreamer_YUV_ConnectInput(TransformFilter *tf, PIN_DIRECTION dir, IPin *pin)
471 return S_OK;
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;
482 if (dir != PINDIR_INPUT)
483 return S_OK;
485 if (Gstreamer_YUV_QueryConnect(&This->tf, amt) == S_FALSE || !amt->pbFormat)
486 return E_FAIL;
488 FreeMediaType(outpmt);
489 CopyMediaType(outpmt, amt);
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;
510 if (!avgtime)
511 avgtime = 10000000 / 30;
513 outpmt->subtype = MEDIASUBTYPE_RGB24;
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);
533 hr = Gstreamer_transform_ConnectInput(This, amt, capsin, capsout);
534 gst_caps_unref(capsin);
535 gst_caps_unref(capsout);
537 This->cbBuffer = width * height * 4;
538 return hr;
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
556 IUnknown * CALLBACK Gstreamer_YUV_create(IUnknown *punkout, HRESULT *phr)
558 IUnknown *obj = NULL;
559 if (!Gstreamer_init())
561 *phr = E_FAIL;
562 return NULL;
564 *phr = Gstreamer_transform_create(punkout, &CLSID_Gstreamer_YUV, "ffmpegcolorspace", &Gstreamer_YUV_vtbl, (LPVOID*)&obj);
565 return obj;
568 HRESULT WINAPI GSTTf_QueryInterface(IBaseFilter * iface, REFIID riid, LPVOID * ppv)
570 HRESULT hr;
571 GstTfImpl *This = (GstTfImpl*)iface;
572 TRACE("(%p/%p)->(%s, %p)\n", This, iface, debugstr_guid(riid), ppv);
574 if (IsEqualIID(riid, &IID_IMediaSeeking))
575 return IUnknown_QueryInterface(This->seekthru_unk, riid, ppv);
577 hr = TransformFilterImpl_QueryInterface(iface, riid, ppv);
579 return hr;
582 static const IBaseFilterVtbl GSTTf_Vtbl =
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