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
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"
46 #include "wine/unicode.h"
47 #include "wine/debug.h"
51 WINE_DEFAULT_DEBUG_CHANNEL(gstreamer
);
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
;
65 if (!GST_IS_ELEMENT_FACTORY(feature
))
67 factory
= GST_ELEMENT_FACTORY(feature
);
68 if (!strstr(gst_element_factory_get_klass(factory
), data
->type
))
70 for (list
= gst_element_factory_get_static_pad_templates(factory
); list
; list
= list
->next
) {
71 GstStaticPadTemplate
*pad
= (GstStaticPadTemplate
*)list
->data
;
74 if (pad
->direction
!= GST_PAD_SINK
)
76 caps
= gst_static_caps_get(&pad
->static_caps
);
77 ret
= gst_caps_is_always_compatible(caps
, data
->caps
);
85 static const char *Gstreamer_FindMatch(const char *strcaps
)
90 GstElementFactory
*bestfactory
= NULL
;
91 GstCaps
*caps
= gst_caps_from_string(strcaps
);
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
;
99 rank
= gst_plugin_feature_get_rank(GST_PLUGIN_FEATURE(factory
));
100 if (rank
> bestrank
|| !bestrank
) {
102 bestfactory
= factory
;
105 gst_caps_unref(caps
);
109 FIXME("Could not find plugin for %s\n", strcaps
);
112 return gst_plugin_feature_get_name(GST_PLUGIN_FEATURE(bestfactory
));
115 typedef struct GstTfImpl
{
117 IUnknown
*seekthru_unk
;
118 const char *gstreamer_name
;
120 GstPad
*my_src
, *my_sink
, *their_src
, *their_sink
;
124 static HRESULT WINAPI
Gstreamer_transform_ProcessBegin(TransformFilter
*iface
) {
125 GstTfImpl
*This
= (GstTfImpl
*)iface
;
128 ret
= gst_element_set_state(This
->filter
, GST_STATE_PLAYING
);
129 TRACE("Returned: %i\n", ret
);
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
;
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
);
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
);
176 return GST_FLOW_WRONG_STATE
;
178 return GST_FLOW_RESEND
;
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
;
187 TRACE("Requesting buffer\n");
189 hr
= BaseOutputPinImpl_GetDeliveryBuffer((BaseOutputPin
*)This
->tf
.ppPins
[1], &sample
, NULL
, NULL
, 0);
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
);
199 IMediaSample_Release(sample
);
200 ERR("Out of memory\n");
201 return GST_FLOW_ERROR
;
204 caps
= gst_pad_get_caps_reffed(This
->my_sink
);
205 gst_buffer_set_caps(*buf
, caps
);
209 static HRESULT WINAPI
Gstreamer_transform_ProcessData(TransformFilter
*iface
, IMediaSample
*sample
) {
210 GstTfImpl
*This
= (GstTfImpl
*)iface
;
211 REFERENCE_TIME tStart
, tStop
;
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
);
222 LeaveCriticalSection(&This
->tf
.filter
.csFilter
);
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
);
230 buf
->timestamp
= tStart
* 100;
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
);
243 WARN("Sending returned: %i\n", ret
);
244 if (ret
== GST_FLOW_ERROR
)
246 if (ret
== GST_FLOW_WRONG_STATE
)
247 return VFW_E_WRONG_STATE
;
248 if (ret
== GST_FLOW_RESEND
)
253 static HRESULT WINAPI
Gstreamer_transform_ProcessEnd(TransformFilter
*iface
) {
254 GstTfImpl
*This
= (GstTfImpl
*)iface
;
257 ret
= gst_element_set_state(This
->filter
, GST_STATE_PAUSED
);
258 TRACE("Returned: %i\n", ret
);
262 static void Gstreamer_transform_pad_added(GstElement
*filter
, GstPad
*pad
, GstTfImpl
*This
)
265 if (!GST_PAD_IS_SRC(pad
))
268 ret
= gst_pad_link(pad
, This
->my_sink
);
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
) {
279 int done
= 0, found
= 0, ret
;
281 This
->filter
= gst_element_factory_make(This
->gstreamer_name
, NULL
);
283 FIXME("Could not make %s filter\n", This
->gstreamer_name
);
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
);
296 WARN("Failed to set caps on own source with %i\n", ret
);
300 ret
= gst_pad_set_caps(This
->my_sink
, capsout
);
302 WARN("Failed to set caps on own sink with %i\n", ret
);
306 it
= gst_element_iterate_sink_pads(This
->filter
);
310 switch (gst_iterator_next(it
, &item
)) {
311 case GST_ITERATOR_RESYNC
:
312 gst_iterator_resync (it
);
314 case GST_ITERATOR_OK
:
315 This
->their_sink
= item
;
316 case GST_ITERATOR_ERROR
:
317 case GST_ITERATOR_DONE
:
322 gst_iterator_free(it
);
323 if (!This
->their_sink
) {
324 ERR("Could not find sink on filter %s\n", This
->gstreamer_name
);
328 it
= gst_element_iterate_src_pads(This
->filter
);
329 gst_iterator_resync(it
);
334 switch (gst_iterator_next(it
, &item
)) {
335 case GST_ITERATOR_RESYNC
:
336 gst_iterator_resync (it
);
338 case GST_ITERATOR_OK
:
339 This
->their_src
= item
;
340 case GST_ITERATOR_ERROR
:
341 case GST_ITERATOR_DONE
:
346 gst_iterator_free(it
);
347 found
= !!This
->their_src
;
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
);
352 WARN("Failed to link with %i\n", ret
);
357 Gstreamer_transform_pad_added(This
->filter
, This
->their_src
, This
);
359 if (!gst_pad_is_linked(This
->my_sink
))
362 TRACE("Connected\n");
366 static HRESULT WINAPI
Gstreamer_transform_Cleanup(TransformFilter
*tf
, PIN_DIRECTION dir
) {
367 GstTfImpl
*This
= (GstTfImpl
*)tf
;
369 if (dir
== PINDIR_INPUT
)
372 gst_element_set_state(This
->filter
, GST_STATE_NULL
);
373 gst_object_unref(This
->filter
);
377 gst_pad_unlink(This
->my_src
, This
->their_sink
);
378 gst_object_unref(This
->my_src
);
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
);
390 static HRESULT WINAPI
Gstreamer_transform_EndOfStream(TransformFilter
*iface
) {
391 GstTfImpl
*This
= (GstTfImpl
*)iface
;
394 gst_pad_push_event(This
->my_src
, gst_event_new_eos());
398 static HRESULT WINAPI
Gstreamer_transform_BeginFlush(TransformFilter
*iface
) {
399 GstTfImpl
*This
= (GstTfImpl
*)iface
;
402 gst_pad_push_event(This
->my_src
, gst_event_new_flush_start());
406 static HRESULT WINAPI
Gstreamer_transform_EndFlush(TransformFilter
*iface
) {
407 GstTfImpl
*This
= (GstTfImpl
*)iface
;
410 gst_pad_push_event(This
->my_src
, gst_event_new_flush_stop());
414 static HRESULT WINAPI
Gstreamer_transform_NewSegment(TransformFilter
*iface
, REFERENCE_TIME tStart
, REFERENCE_TIME tStop
, double dRate
) {
415 GstTfImpl
*This
= (GstTfImpl
*)iface
;
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));
423 static HRESULT
Gstreamer_transform_create(IUnknown
*punkout
, const CLSID
*clsid
, const char *name
, const TransformFilterFuncTable
*vtbl
, void **obj
)
427 if (FAILED(TransformFilter_Construct(&GSTTf_Vtbl
, sizeof(GstTfImpl
), clsid
, vtbl
, (IBaseFilter
**)&This
)))
428 return E_OUTOFMEMORY
;
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
;
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
)))
453 if (memcmp(&amt
->subtype
.Data2
, &MEDIATYPE_Video
.Data2
, sizeof(GUID
) - sizeof(amt
->subtype
.Data1
)))
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'):
464 WARN("Unhandled fourcc %s\n", debugstr_an((char*)&amt
->subtype
.Data1
, 4));
469 static HRESULT WINAPI
Gstreamer_YUV_ConnectInput(TransformFilter
*tf
, PIN_DIRECTION dir
, IPin
*pin
)
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
;
482 if (dir
!= PINDIR_INPUT
)
485 if (Gstreamer_YUV_QueryConnect(&This
->tf
, amt
) == S_FALSE
|| !amt
->pbFormat
)
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
;
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
;
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
,
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,
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;
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())
564 *phr
= Gstreamer_transform_create(punkout
, &CLSID_Gstreamer_YUV
, "ffmpegcolorspace", &Gstreamer_YUV_vtbl
, (LPVOID
*)&obj
);
568 HRESULT WINAPI
GSTTf_QueryInterface(IBaseFilter
* iface
, REFIID riid
, LPVOID
* ppv
)
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
);
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