|  | /* | 
|  | * GStreamer wrapper filter | 
|  | * | 
|  | * Copyright 2010 Maarten Lankhorst for CodeWeavers | 
|  | * Copyright 2010 Aric Stewart for CodeWeavers | 
|  | * | 
|  | * 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 "config.h" | 
|  |  | 
|  | #include <gst/app/gstappsink.h> | 
|  | #include <gst/app/gstappsrc.h> | 
|  | #include <gst/app/gstappbuffer.h> | 
|  |  | 
|  | #include "gst_private.h" | 
|  | #include "gst_guids.h" | 
|  |  | 
|  | #include "uuids.h" | 
|  | #include "mmreg.h" | 
|  | #include "windef.h" | 
|  | #include "winbase.h" | 
|  | #include "dshow.h" | 
|  | #include "strmif.h" | 
|  | #include "vfwmsgs.h" | 
|  | #include "dvdmedia.h" | 
|  | #include "ks.h" | 
|  | #include "ksmedia.h" | 
|  | #include "msacm.h" | 
|  |  | 
|  | #include <assert.h> | 
|  |  | 
|  | #include "wine/unicode.h" | 
|  | #include "wine/debug.h" | 
|  |  | 
|  | #include "initguid.h" | 
|  | DEFINE_GUID(WMMEDIASUBTYPE_MP3, 0x00000055, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); | 
|  |  | 
|  | WINE_DEFAULT_DEBUG_CHANNEL(gstreamer); | 
|  |  | 
|  | struct typeinfo { | 
|  | GstCaps *caps; | 
|  | const char *type; | 
|  | }; | 
|  |  | 
|  | static const IBaseFilterVtbl GSTTf_Vtbl; | 
|  |  | 
|  | static gboolean match_element(GstPluginFeature *feature, gpointer gdata) { | 
|  | struct typeinfo *data = (struct typeinfo*)gdata; | 
|  | GstElementFactory *factory; | 
|  | const GList *list; | 
|  |  | 
|  | if (!GST_IS_ELEMENT_FACTORY(feature)) | 
|  | return FALSE; | 
|  | factory = GST_ELEMENT_FACTORY(feature); | 
|  | if (!strstr(gst_element_factory_get_klass(factory), data->type)) | 
|  | return FALSE; | 
|  | for (list = gst_element_factory_get_static_pad_templates(factory); list; list = list->next) { | 
|  | GstStaticPadTemplate *pad = (GstStaticPadTemplate*)list->data; | 
|  | GstCaps *caps; | 
|  | gboolean ret; | 
|  | if (pad->direction != GST_PAD_SINK) | 
|  | continue; | 
|  | caps = gst_static_caps_get(&pad->static_caps); | 
|  | ret = gst_caps_is_always_compatible(caps, data->caps); | 
|  | gst_caps_unref(caps); | 
|  | if (ret) | 
|  | return TRUE; | 
|  | } | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | static const char *Gstreamer_FindMatch(const char *strcaps) | 
|  | { | 
|  | struct typeinfo data; | 
|  | GList *list, *copy; | 
|  | guint bestrank = 0; | 
|  | GstElementFactory *bestfactory = NULL; | 
|  | GstCaps *caps = gst_caps_from_string(strcaps); | 
|  |  | 
|  | data.caps = caps; | 
|  | data.type = "Decoder"; | 
|  | copy = gst_default_registry_feature_filter(match_element, 0, &data); | 
|  | for (list = copy; list; list = list->next) { | 
|  | GstElementFactory *factory = (GstElementFactory*)list->data; | 
|  | guint rank; | 
|  | rank = gst_plugin_feature_get_rank(GST_PLUGIN_FEATURE(factory)); | 
|  | if (rank > bestrank || !bestrank) { | 
|  | bestrank = rank; | 
|  | bestfactory = factory; | 
|  | } | 
|  | } | 
|  | gst_caps_unref(caps); | 
|  | g_list_free(copy); | 
|  |  | 
|  | if (!bestfactory) { | 
|  | FIXME("Could not find plugin for %s\n", strcaps); | 
|  | return NULL; | 
|  | } | 
|  | return gst_plugin_feature_get_name(GST_PLUGIN_FEATURE(bestfactory)); | 
|  | } | 
|  |  | 
|  | typedef struct GstTfImpl { | 
|  | TransformFilter tf; | 
|  | IUnknown *seekthru_unk; | 
|  | const char *gstreamer_name; | 
|  | GstElement *filter; | 
|  | GstPad *my_src, *my_sink, *their_src, *their_sink; | 
|  | LONG cbBuffer; | 
|  | } GstTfImpl; | 
|  |  | 
|  | static HRESULT WINAPI Gstreamer_transform_ProcessBegin(TransformFilter *iface) { | 
|  | GstTfImpl *This = (GstTfImpl*)iface; | 
|  | int ret; | 
|  |  | 
|  | ret = gst_element_set_state(This->filter, GST_STATE_PLAYING); | 
|  | TRACE("Returned: %i\n", ret); | 
|  | return S_OK; | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI Gstreamer_transform_DecideBufferSize(TransformFilter *tf, IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *ppropInputRequest) | 
|  | { | 
|  | GstTfImpl *This = (GstTfImpl*)tf; | 
|  | ALLOCATOR_PROPERTIES actual; | 
|  |  | 
|  | if (!ppropInputRequest->cbAlign) | 
|  | ppropInputRequest->cbAlign = 1; | 
|  |  | 
|  | ppropInputRequest->cbBuffer = This->cbBuffer; | 
|  |  | 
|  | if (ppropInputRequest->cBuffers < 2) | 
|  | ppropInputRequest->cBuffers = 2; | 
|  |  | 
|  | return IMemAllocator_SetProperties(pAlloc, ppropInputRequest, &actual); | 
|  | } | 
|  |  | 
|  | static void release_sample(void *data) { | 
|  | TRACE("Releasing %p\n", data); | 
|  | IMediaSample_Release((IMediaSample *)data); | 
|  | } | 
|  |  | 
|  | static GstFlowReturn got_data(GstPad *pad, GstBuffer *buf) { | 
|  | GstTfImpl *This = gst_pad_get_element_private(pad); | 
|  | IMediaSample *sample = GST_APP_BUFFER(buf)->priv; | 
|  | REFERENCE_TIME tStart, tStop; | 
|  | HRESULT hr; | 
|  |  | 
|  | if (GST_BUFFER_TIMESTAMP_IS_VALID(buf) && | 
|  | GST_BUFFER_DURATION_IS_VALID(buf)) { | 
|  | tStart = buf->timestamp / 100; | 
|  | tStop = tStart + buf->duration / 100; | 
|  | IMediaSample_SetTime(sample, &tStart, &tStop); | 
|  | } | 
|  | else | 
|  | IMediaSample_SetTime(sample, NULL, NULL); | 
|  | if (GST_BUFFER_OFFSET_IS_VALID(buf) && | 
|  | GST_BUFFER_OFFSET_END_IS_VALID(buf)) { | 
|  | tStart = buf->offset / 100; | 
|  | tStop = buf->offset_end / 100; | 
|  | IMediaSample_SetMediaTime(sample, &tStart, &tStop); | 
|  | } | 
|  | else | 
|  | IMediaSample_SetMediaTime(sample, NULL, NULL); | 
|  |  | 
|  | IMediaSample_SetDiscontinuity(sample, GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_DISCONT)); | 
|  | IMediaSample_SetPreroll(sample, GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_PREROLL)); | 
|  | IMediaSample_SetSyncPoint(sample, !GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_DELTA_UNIT)); | 
|  | IMediaSample_SetActualDataLength(sample, GST_BUFFER_SIZE(buf)); | 
|  |  | 
|  | hr = BaseOutputPinImpl_Deliver((BaseOutputPin*)This->tf.ppPins[1], sample); | 
|  | gst_buffer_unref(buf); | 
|  | if (FAILED(hr)) | 
|  | return GST_FLOW_WRONG_STATE; | 
|  | if (hr != S_OK) | 
|  | return GST_FLOW_RESEND; | 
|  | return GST_FLOW_OK; | 
|  | } | 
|  |  | 
|  | static GstFlowReturn request_buffer(GstPad *pad, guint64 ofs, guint size, GstCaps *caps, GstBuffer **buf) { | 
|  | GstTfImpl *This = gst_pad_get_element_private(pad); | 
|  | IMediaSample *sample; | 
|  | BYTE *ptr; | 
|  | HRESULT hr; | 
|  | TRACE("Requesting buffer\n"); | 
|  |  | 
|  | hr = BaseOutputPinImpl_GetDeliveryBuffer((BaseOutputPin*)This->tf.ppPins[1], &sample, NULL, NULL, 0); | 
|  | if (FAILED(hr)) { | 
|  | ERR("Could not get output buffer: %08x\n", hr); | 
|  | return GST_FLOW_WRONG_STATE; | 
|  | } | 
|  | IMediaSample_SetActualDataLength(sample, size); | 
|  | IMediaSample_GetPointer(sample, &ptr); | 
|  | *buf = gst_app_buffer_new(ptr, size, release_sample, sample); | 
|  |  | 
|  | if (!*buf) { | 
|  | IMediaSample_Release(sample); | 
|  | ERR("Out of memory\n"); | 
|  | return GST_FLOW_ERROR; | 
|  | } | 
|  | if (!caps) | 
|  | caps = gst_pad_get_caps_reffed(This->my_sink); | 
|  | gst_buffer_set_caps(*buf, caps); | 
|  | return GST_FLOW_OK; | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI Gstreamer_transform_ProcessData(TransformFilter *iface, IMediaSample *sample) { | 
|  | GstTfImpl *This = (GstTfImpl*)iface; | 
|  | REFERENCE_TIME tStart, tStop; | 
|  | BYTE *data; | 
|  | GstBuffer *buf; | 
|  | HRESULT hr; | 
|  | int ret; | 
|  | TRACE("Reading %p\n", sample); | 
|  |  | 
|  | EnterCriticalSection(&This->tf.filter.csFilter); | 
|  | IMediaSample_GetPointer(sample, &data); | 
|  | buf = gst_app_buffer_new(data, IMediaSample_GetActualDataLength(sample), release_sample, sample); | 
|  | if (!buf) { | 
|  | LeaveCriticalSection(&This->tf.filter.csFilter); | 
|  | return S_OK; | 
|  | } | 
|  | gst_buffer_set_caps(buf, gst_pad_get_caps_reffed(This->my_src)); | 
|  | IMediaSample_AddRef(sample); | 
|  | buf->duration = buf->timestamp = -1; | 
|  | hr = IMediaSample_GetTime(sample, &tStart, &tStop); | 
|  | if (SUCCEEDED(hr)) { | 
|  | buf->timestamp = tStart * 100; | 
|  | if (hr == S_OK) | 
|  | buf->duration = (tStop - tStart)*100; | 
|  | } | 
|  | if (IMediaSample_GetMediaTime(sample, &tStart, &tStop) == S_OK) { | 
|  | buf->offset = tStart * 100; | 
|  | buf->offset_end = tStop * 100; | 
|  | } | 
|  | if (IMediaSample_IsDiscontinuity(sample) == S_OK) | 
|  | GST_BUFFER_FLAG_SET(buf, GST_BUFFER_FLAG_DISCONT); | 
|  | if (IMediaSample_IsPreroll(sample) == S_OK) | 
|  | GST_BUFFER_FLAG_SET(buf, GST_BUFFER_FLAG_PREROLL); | 
|  | if (IMediaSample_IsSyncPoint(sample) != S_OK) | 
|  | GST_BUFFER_FLAG_SET(buf, GST_BUFFER_FLAG_DELTA_UNIT); | 
|  | LeaveCriticalSection(&This->tf.filter.csFilter); | 
|  | ret = gst_pad_push(This->my_src, buf); | 
|  | if (ret) | 
|  | WARN("Sending returned: %i\n", ret); | 
|  | if (ret == GST_FLOW_ERROR) | 
|  | return E_FAIL; | 
|  | if (ret == GST_FLOW_WRONG_STATE) | 
|  | return VFW_E_WRONG_STATE; | 
|  | if (ret == GST_FLOW_RESEND) | 
|  | return S_FALSE; | 
|  | return S_OK; | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI Gstreamer_transform_ProcessEnd(TransformFilter *iface) { | 
|  | GstTfImpl *This = (GstTfImpl*)iface; | 
|  | int ret; | 
|  |  | 
|  | LeaveCriticalSection(&This->tf.filter.csFilter); | 
|  | ret = gst_element_set_state(This->filter, GST_STATE_READY); | 
|  | EnterCriticalSection(&This->tf.filter.csFilter); | 
|  | TRACE("Returned: %i\n", ret); | 
|  | return S_OK; | 
|  | } | 
|  |  | 
|  | static void Gstreamer_transform_pad_added(GstElement *filter, GstPad *pad, GstTfImpl *This) | 
|  | { | 
|  | int ret; | 
|  | if (!GST_PAD_IS_SRC(pad)) | 
|  | return; | 
|  |  | 
|  | ret = gst_pad_link(pad, This->my_sink); | 
|  | if (ret < 0) | 
|  | WARN("Failed to link with %i\n", ret); | 
|  | This->their_src = pad; | 
|  |  | 
|  | gst_pad_set_active(pad, TRUE); | 
|  | gst_pad_set_active(This->my_sink, TRUE); | 
|  | } | 
|  |  | 
|  | static HRESULT Gstreamer_transform_ConnectInput(GstTfImpl *This, const AM_MEDIA_TYPE *amt, GstCaps *capsin, GstCaps *capsout) { | 
|  | GstIterator *it; | 
|  | int done = 0, found = 0, ret; | 
|  |  | 
|  | This->filter = gst_element_factory_make(This->gstreamer_name, NULL); | 
|  | if (!This->filter) { | 
|  | FIXME("Could not make %s filter\n", This->gstreamer_name); | 
|  | return E_FAIL; | 
|  | } | 
|  | This->my_src = gst_pad_new(NULL, GST_PAD_SRC); | 
|  | gst_pad_set_element_private (This->my_src, This); | 
|  |  | 
|  | This->my_sink = gst_pad_new(NULL, GST_PAD_SINK); | 
|  | gst_pad_set_chain_function(This->my_sink, got_data); | 
|  | gst_pad_set_bufferalloc_function(This->my_sink, request_buffer); | 
|  | gst_pad_set_element_private (This->my_sink, This); | 
|  |  | 
|  | ret = gst_pad_set_caps(This->my_src, capsin); | 
|  | if (ret < 0) { | 
|  | WARN("Failed to set caps on own source with %i\n", ret); | 
|  | return E_FAIL; | 
|  | } | 
|  |  | 
|  | ret = gst_pad_set_caps(This->my_sink, capsout); | 
|  | if (ret < 0) { | 
|  | WARN("Failed to set caps on own sink with %i\n", ret); | 
|  | return E_FAIL; | 
|  | } | 
|  |  | 
|  | it = gst_element_iterate_sink_pads(This->filter); | 
|  | while (!done) { | 
|  | gpointer item; | 
|  |  | 
|  | switch (gst_iterator_next(it, &item)) { | 
|  | case GST_ITERATOR_RESYNC: | 
|  | gst_iterator_resync (it); | 
|  | break; | 
|  | case GST_ITERATOR_OK: | 
|  | This->their_sink = item; | 
|  | case GST_ITERATOR_ERROR: | 
|  | case GST_ITERATOR_DONE: | 
|  | done = 1; | 
|  | break; | 
|  | } | 
|  | } | 
|  | gst_iterator_free(it); | 
|  | if (!This->their_sink) { | 
|  | ERR("Could not find sink on filter %s\n", This->gstreamer_name); | 
|  | return E_FAIL; | 
|  | } | 
|  |  | 
|  | it = gst_element_iterate_src_pads(This->filter); | 
|  | gst_iterator_resync(it); | 
|  | done = 0; | 
|  | while (!done) { | 
|  | gpointer item; | 
|  |  | 
|  | switch (gst_iterator_next(it, &item)) { | 
|  | case GST_ITERATOR_RESYNC: | 
|  | gst_iterator_resync (it); | 
|  | break; | 
|  | case GST_ITERATOR_OK: | 
|  | This->their_src = item; | 
|  | case GST_ITERATOR_ERROR: | 
|  | case GST_ITERATOR_DONE: | 
|  | done = 1; | 
|  | break; | 
|  | } | 
|  | } | 
|  | gst_iterator_free(it); | 
|  | found = !!This->their_src; | 
|  | if (!found) | 
|  | g_signal_connect(This->filter, "pad-added", G_CALLBACK(Gstreamer_transform_pad_added), This); | 
|  | ret = gst_pad_link(This->my_src, This->their_sink); | 
|  | if (ret < 0) { | 
|  | WARN("Failed to link with %i\n", ret); | 
|  | return E_FAIL; | 
|  | } | 
|  |  | 
|  | if (found) | 
|  | Gstreamer_transform_pad_added(This->filter, This->their_src, This); | 
|  |  | 
|  | if (!gst_pad_is_linked(This->my_sink)) | 
|  | return E_FAIL; | 
|  |  | 
|  | TRACE("Connected\n"); | 
|  | return S_OK; | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI Gstreamer_transform_Cleanup(TransformFilter *tf, PIN_DIRECTION dir) { | 
|  | GstTfImpl *This = (GstTfImpl*)tf; | 
|  |  | 
|  | if (dir == PINDIR_INPUT) | 
|  | { | 
|  | if (This->filter) { | 
|  | gst_element_set_state(This->filter, GST_STATE_NULL); | 
|  | gst_object_unref(This->filter); | 
|  | } | 
|  | This->filter = NULL; | 
|  | if (This->my_src) { | 
|  | gst_pad_unlink(This->my_src, This->their_sink); | 
|  | gst_object_unref(This->my_src); | 
|  | } | 
|  | if (This->my_sink) { | 
|  | gst_pad_unlink(This->their_src, This->my_sink); | 
|  | gst_object_unref(This->my_sink); | 
|  | } | 
|  | This->my_sink = This->my_src = This->their_sink = This->their_src = NULL; | 
|  | FIXME("%p stub\n", This); | 
|  | } | 
|  | return S_OK; | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI Gstreamer_transform_EndOfStream(TransformFilter *iface) { | 
|  | GstTfImpl *This = (GstTfImpl*)iface; | 
|  | TRACE("%p\n", This); | 
|  |  | 
|  | gst_pad_push_event(This->my_src, gst_event_new_eos()); | 
|  | return S_OK; | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI Gstreamer_transform_BeginFlush(TransformFilter *iface) { | 
|  | GstTfImpl *This = (GstTfImpl*)iface; | 
|  | TRACE("%p\n", This); | 
|  |  | 
|  | gst_pad_push_event(This->my_src, gst_event_new_flush_start()); | 
|  | return S_OK; | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI Gstreamer_transform_EndFlush(TransformFilter *iface) { | 
|  | GstTfImpl *This = (GstTfImpl*)iface; | 
|  | TRACE("%p\n", This); | 
|  |  | 
|  | gst_pad_push_event(This->my_src, gst_event_new_flush_stop()); | 
|  | return S_OK; | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI Gstreamer_transform_NewSegment(TransformFilter *iface, REFERENCE_TIME tStart, REFERENCE_TIME tStop, double dRate) { | 
|  | GstTfImpl *This = (GstTfImpl*)iface; | 
|  | TRACE("%p\n", This); | 
|  |  | 
|  | gst_pad_push_event(This->my_src, gst_event_new_new_segment_full(1, | 
|  | 1.0, dRate, GST_FORMAT_TIME, 0, tStop <= tStart ? -1 : tStop * 100, tStart*100)); | 
|  | return S_OK; | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI Gstreamer_transform_QOS(TransformFilter *iface, IBaseFilter *sender, Quality qm) { | 
|  | GstTfImpl *This = (GstTfImpl*)iface; | 
|  | REFERENCE_TIME late = qm.Late; | 
|  | if (qm.Late < 0 && -qm.Late > qm.TimeStamp) | 
|  | late = -qm.TimeStamp; | 
|  | gst_pad_push_event(This->my_sink, gst_event_new_qos(1000. / qm.Proportion, late * 100, qm.TimeStamp * 100)); | 
|  | return QualityControlImpl_Notify((IQualityControl*)&iface->qcimpl, sender, qm); | 
|  | } | 
|  |  | 
|  | static HRESULT Gstreamer_transform_create(IUnknown *punkout, const CLSID *clsid, const char *name, const TransformFilterFuncTable *vtbl, void **obj) | 
|  | { | 
|  | GstTfImpl *This; | 
|  |  | 
|  | if (FAILED(TransformFilter_Construct(&GSTTf_Vtbl, sizeof(GstTfImpl), clsid, vtbl, (IBaseFilter**)&This))) | 
|  | return E_OUTOFMEMORY; | 
|  | else | 
|  | { | 
|  | ISeekingPassThru *passthru; | 
|  | CoCreateInstance(&CLSID_SeekingPassThru, (IUnknown*)This, CLSCTX_INPROC_SERVER, &IID_IUnknown, (void**)&This->seekthru_unk); | 
|  | IUnknown_QueryInterface(This->seekthru_unk, &IID_ISeekingPassThru, (void**)&passthru); | 
|  | ISeekingPassThru_Init(passthru, FALSE, (IPin*)This->tf.ppPins[0]); | 
|  | ISeekingPassThru_Release(passthru); | 
|  | } | 
|  |  | 
|  | This->gstreamer_name = name; | 
|  | *obj = This; | 
|  |  | 
|  | return S_OK; | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI Gstreamer_Mp3_QueryConnect(TransformFilter *iface, const AM_MEDIA_TYPE *amt) { | 
|  | GstTfImpl *This = (GstTfImpl*)iface; | 
|  | TRACE("%p %p\n", This, amt); | 
|  | dump_AM_MEDIA_TYPE(amt); | 
|  |  | 
|  | if ( (!IsEqualGUID(&amt->majortype, &MEDIATYPE_Audio) && | 
|  | !IsEqualGUID(&amt->majortype, &MEDIATYPE_Stream)) || | 
|  | (!IsEqualGUID(&amt->subtype, &MEDIASUBTYPE_MPEG1AudioPayload) && | 
|  | !IsEqualGUID(&amt->subtype, &WMMEDIASUBTYPE_MP3)) | 
|  | || !IsEqualGUID(&amt->formattype, &FORMAT_WaveFormatEx)) | 
|  | return S_FALSE; | 
|  |  | 
|  | return S_OK; | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI Gstreamer_Mp3_SetMediaType(TransformFilter *tf, PIN_DIRECTION dir, const AM_MEDIA_TYPE *amt) { | 
|  | GstTfImpl *This = (GstTfImpl*)tf; | 
|  | GstCaps *capsin, *capsout; | 
|  | AM_MEDIA_TYPE *outpmt = &This->tf.pmt; | 
|  | WAVEFORMATEX *wfx, *wfxin; | 
|  | HRESULT hr; | 
|  | int layer; | 
|  |  | 
|  | if (dir != PINDIR_INPUT) | 
|  | return S_OK; | 
|  |  | 
|  | if (Gstreamer_Mp3_QueryConnect(&This->tf, amt) == S_FALSE || !amt->pbFormat) | 
|  | return VFW_E_TYPE_NOT_ACCEPTED; | 
|  |  | 
|  | wfxin = (WAVEFORMATEX*)amt->pbFormat; | 
|  | switch (wfxin->wFormatTag) { | 
|  | case WAVE_FORMAT_MPEGLAYER3: | 
|  | layer = 3; | 
|  | break; | 
|  | case WAVE_FORMAT_MPEG: { | 
|  | MPEG1WAVEFORMAT *mpgformat = (MPEG1WAVEFORMAT*)wfxin; | 
|  | layer = mpgformat->fwHeadLayer; | 
|  | break; | 
|  | } | 
|  | default: | 
|  | FIXME("Unhandled tag %x\n", wfxin->wFormatTag); | 
|  | return E_FAIL; | 
|  | } | 
|  |  | 
|  | FreeMediaType(outpmt); | 
|  | CopyMediaType(outpmt, amt); | 
|  |  | 
|  | outpmt->subtype = MEDIASUBTYPE_PCM; | 
|  | outpmt->formattype = FORMAT_WaveFormatEx; | 
|  | outpmt->cbFormat = sizeof(*wfx); | 
|  | CoTaskMemFree(outpmt->pbFormat); | 
|  | wfx = CoTaskMemAlloc(outpmt->cbFormat); | 
|  | outpmt->pbFormat = (BYTE*)wfx; | 
|  | wfx->wFormatTag = WAVE_FORMAT_PCM; | 
|  | wfx->wBitsPerSample = 16; | 
|  | wfx->nSamplesPerSec = wfxin->nSamplesPerSec; | 
|  | wfx->nChannels = wfxin->nChannels; | 
|  | wfx->nBlockAlign = wfx->wBitsPerSample * wfx->nChannels / 8; | 
|  | wfx->cbSize = 0; | 
|  | wfx->nAvgBytesPerSec = wfx->nSamplesPerSec * wfx->nBlockAlign; | 
|  |  | 
|  | capsin = gst_caps_new_simple("audio/mpeg", | 
|  | "mpegversion", G_TYPE_INT, 1, | 
|  | "layer", G_TYPE_INT, layer, | 
|  | "rate", G_TYPE_INT, wfx->nSamplesPerSec, | 
|  | "channels", G_TYPE_INT, wfx->nChannels, | 
|  | NULL); | 
|  | capsout = gst_caps_new_simple("audio/x-raw-int", | 
|  | "endianness", G_TYPE_INT, 1234, | 
|  | "signed", G_TYPE_BOOLEAN, 1, | 
|  | "width", G_TYPE_INT, 16, | 
|  | "depth", G_TYPE_INT, 16, | 
|  | "rate", G_TYPE_INT, wfx->nSamplesPerSec, | 
|  | "channels", G_TYPE_INT, wfx->nChannels, | 
|  | NULL); | 
|  |  | 
|  | hr = Gstreamer_transform_ConnectInput(This, amt, capsin, capsout); | 
|  | gst_caps_unref(capsin); | 
|  | gst_caps_unref(capsout); | 
|  |  | 
|  | This->cbBuffer = wfx->nAvgBytesPerSec / 4; | 
|  |  | 
|  | return hr; | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI Gstreamer_Mp3_ConnectInput(TransformFilter *tf, PIN_DIRECTION dir, IPin *pin) | 
|  | { | 
|  | return S_OK; | 
|  | } | 
|  |  | 
|  | static const TransformFilterFuncTable Gstreamer_Mp3_vtbl = { | 
|  | Gstreamer_transform_DecideBufferSize, | 
|  | Gstreamer_transform_ProcessBegin, | 
|  | Gstreamer_transform_ProcessData, | 
|  | Gstreamer_transform_ProcessEnd, | 
|  | Gstreamer_Mp3_QueryConnect, | 
|  | Gstreamer_Mp3_SetMediaType, | 
|  | Gstreamer_Mp3_ConnectInput, | 
|  | Gstreamer_transform_Cleanup, | 
|  | Gstreamer_transform_EndOfStream, | 
|  | Gstreamer_transform_BeginFlush, | 
|  | Gstreamer_transform_EndFlush, | 
|  | Gstreamer_transform_NewSegment, | 
|  | Gstreamer_transform_QOS | 
|  | }; | 
|  |  | 
|  | IUnknown * CALLBACK Gstreamer_Mp3_create(IUnknown *punkout, HRESULT *phr) | 
|  | { | 
|  | const char *plugin; | 
|  | IUnknown *obj = NULL; | 
|  | if (!Gstreamer_init()) | 
|  | { | 
|  | *phr = E_FAIL; | 
|  | return NULL; | 
|  | } | 
|  | plugin = Gstreamer_FindMatch("audio/mpeg, mpegversion=(int) 1"); | 
|  | if (!plugin) | 
|  | { | 
|  | *phr = E_FAIL; | 
|  | return NULL; | 
|  | } | 
|  | *phr = Gstreamer_transform_create(punkout, &CLSID_Gstreamer_Mp3, plugin, &Gstreamer_Mp3_vtbl, (LPVOID*)&obj); | 
|  | return obj; | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI Gstreamer_YUV_QueryConnect(TransformFilter *iface, const AM_MEDIA_TYPE *amt) { | 
|  | GstTfImpl *This = (GstTfImpl*)iface; | 
|  | TRACE("%p %p\n", This, amt); | 
|  | dump_AM_MEDIA_TYPE(amt); | 
|  |  | 
|  | if (!IsEqualGUID(&amt->majortype, &MEDIATYPE_Video) || | 
|  | (!IsEqualGUID(&amt->formattype, &FORMAT_VideoInfo) && | 
|  | !IsEqualGUID(&amt->formattype, &FORMAT_VideoInfo2))) | 
|  | return S_FALSE; | 
|  | if (memcmp(&amt->subtype.Data2, &MEDIATYPE_Video.Data2, sizeof(GUID) - sizeof(amt->subtype.Data1))) | 
|  | return S_FALSE; | 
|  | switch (amt->subtype.Data1) { | 
|  | case mmioFOURCC('I','4','2','0'): | 
|  | case mmioFOURCC('Y','V','1','2'): | 
|  | case mmioFOURCC('N','V','1','2'): | 
|  | case mmioFOURCC('N','V','2','1'): | 
|  | case mmioFOURCC('Y','U','Y','2'): | 
|  | case mmioFOURCC('Y','V','Y','U'): | 
|  | return S_OK; | 
|  | default: | 
|  | WARN("Unhandled fourcc %s\n", debugstr_an((char*)&amt->subtype.Data1, 4)); | 
|  | return S_FALSE; | 
|  | } | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI Gstreamer_YUV_ConnectInput(TransformFilter *tf, PIN_DIRECTION dir, IPin *pin) | 
|  | { | 
|  | return S_OK; | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI Gstreamer_YUV_SetMediaType(TransformFilter *tf, PIN_DIRECTION dir,  const AM_MEDIA_TYPE *amt) { | 
|  | GstTfImpl *This = (GstTfImpl*)tf; | 
|  | GstCaps *capsin, *capsout; | 
|  | AM_MEDIA_TYPE *outpmt = &This->tf.pmt; | 
|  | HRESULT hr; | 
|  | int avgtime; | 
|  | DWORD width, height; | 
|  |  | 
|  | if (dir != PINDIR_INPUT) | 
|  | return S_OK; | 
|  |  | 
|  | if (Gstreamer_YUV_QueryConnect(&This->tf, amt) == S_FALSE || !amt->pbFormat) | 
|  | return E_FAIL; | 
|  |  | 
|  | FreeMediaType(outpmt); | 
|  | CopyMediaType(outpmt, amt); | 
|  |  | 
|  | if (IsEqualGUID(&amt->formattype, &FORMAT_VideoInfo)) { | 
|  | VIDEOINFOHEADER *vih = (VIDEOINFOHEADER*)outpmt->pbFormat; | 
|  | avgtime = vih->AvgTimePerFrame; | 
|  | width = vih->bmiHeader.biWidth; | 
|  | height = vih->bmiHeader.biHeight; | 
|  | if ((LONG)vih->bmiHeader.biHeight > 0) | 
|  | vih->bmiHeader.biHeight = -vih->bmiHeader.biHeight; | 
|  | vih->bmiHeader.biBitCount = 24; | 
|  | vih->bmiHeader.biCompression = BI_RGB; | 
|  | } else { | 
|  | VIDEOINFOHEADER2 *vih = (VIDEOINFOHEADER2*)outpmt->pbFormat; | 
|  | avgtime = vih->AvgTimePerFrame; | 
|  | width = vih->bmiHeader.biWidth; | 
|  | height = vih->bmiHeader.biHeight; | 
|  | if ((LONG)vih->bmiHeader.biHeight > 0) | 
|  | vih->bmiHeader.biHeight = -vih->bmiHeader.biHeight; | 
|  | vih->bmiHeader.biBitCount = 24; | 
|  | vih->bmiHeader.biCompression = BI_RGB; | 
|  | } | 
|  | if (!avgtime) | 
|  | avgtime = 10000000 / 30; | 
|  |  | 
|  | outpmt->subtype = MEDIASUBTYPE_RGB24; | 
|  |  | 
|  | capsin = gst_caps_new_simple("video/x-raw-yuv", | 
|  | "format", GST_TYPE_FOURCC, amt->subtype.Data1, | 
|  | "width", G_TYPE_INT, width, | 
|  | "height", G_TYPE_INT, height, | 
|  | "framerate", GST_TYPE_FRACTION, 10000000, avgtime, | 
|  | NULL); | 
|  | capsout = gst_caps_new_simple("video/x-raw-rgb", | 
|  | "endianness", G_TYPE_INT, 4321, | 
|  | "width", G_TYPE_INT, width, | 
|  | "height", G_TYPE_INT, height, | 
|  | "framerate", GST_TYPE_FRACTION, 10000000, avgtime, | 
|  | "bpp", G_TYPE_INT, 24, | 
|  | "depth", G_TYPE_INT, 24, | 
|  | "red_mask", G_TYPE_INT, 0xff, | 
|  | "green_mask", G_TYPE_INT, 0xff00, | 
|  | "blue_mask", G_TYPE_INT, 0xff0000, | 
|  | NULL); | 
|  |  | 
|  | hr = Gstreamer_transform_ConnectInput(This, amt, capsin, capsout); | 
|  | gst_caps_unref(capsin); | 
|  | gst_caps_unref(capsout); | 
|  |  | 
|  | This->cbBuffer = width * height * 4; | 
|  | return hr; | 
|  | } | 
|  |  | 
|  | static const TransformFilterFuncTable Gstreamer_YUV_vtbl = { | 
|  | Gstreamer_transform_DecideBufferSize, | 
|  | Gstreamer_transform_ProcessBegin, | 
|  | Gstreamer_transform_ProcessData, | 
|  | Gstreamer_transform_ProcessEnd, | 
|  | Gstreamer_YUV_QueryConnect, | 
|  | Gstreamer_YUV_SetMediaType, | 
|  | Gstreamer_YUV_ConnectInput, | 
|  | Gstreamer_transform_Cleanup, | 
|  | Gstreamer_transform_EndOfStream, | 
|  | Gstreamer_transform_BeginFlush, | 
|  | Gstreamer_transform_EndFlush, | 
|  | Gstreamer_transform_NewSegment, | 
|  | Gstreamer_transform_QOS | 
|  | }; | 
|  |  | 
|  | IUnknown * CALLBACK Gstreamer_YUV_create(IUnknown *punkout, HRESULT *phr) | 
|  | { | 
|  | IUnknown *obj = NULL; | 
|  | if (!Gstreamer_init()) | 
|  | { | 
|  | *phr = E_FAIL; | 
|  | return NULL; | 
|  | } | 
|  | *phr = Gstreamer_transform_create(punkout, &CLSID_Gstreamer_YUV, "ffmpegcolorspace", &Gstreamer_YUV_vtbl, (LPVOID*)&obj); | 
|  | return obj; | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI Gstreamer_AudioConvert_QueryConnect(TransformFilter *iface, const AM_MEDIA_TYPE *amt) { | 
|  | GstTfImpl *This = (GstTfImpl*)iface; | 
|  | TRACE("%p %p\n", This, amt); | 
|  | dump_AM_MEDIA_TYPE(amt); | 
|  |  | 
|  | if (!IsEqualGUID(&amt->majortype, &MEDIATYPE_Audio) || | 
|  | !IsEqualGUID(&amt->subtype, &MEDIASUBTYPE_PCM) || | 
|  | !IsEqualGUID(&amt->formattype, &FORMAT_WaveFormatEx)) | 
|  | return S_FALSE; | 
|  | return S_OK; | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI Gstreamer_AudioConvert_ConnectInput(TransformFilter *tf, PIN_DIRECTION dir, IPin *pin) | 
|  | { | 
|  | return S_OK; | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI Gstreamer_AudioConvert_SetMediaType(TransformFilter *tf, PIN_DIRECTION dir, const AM_MEDIA_TYPE *amt) { | 
|  | GstTfImpl *This = (GstTfImpl*)tf; | 
|  | GstCaps *capsin, *capsout; | 
|  | AM_MEDIA_TYPE *outpmt = &This->tf.pmt; | 
|  | WAVEFORMATEX *inwfe; | 
|  | WAVEFORMATEX *outwfe; | 
|  | WAVEFORMATEXTENSIBLE *outwfx; | 
|  | HRESULT hr; | 
|  | int inisfloat = 0, indepth; | 
|  |  | 
|  | if (dir != PINDIR_INPUT) | 
|  | return S_OK; | 
|  |  | 
|  | if (Gstreamer_AudioConvert_QueryConnect(&This->tf, amt) == S_FALSE || !amt->pbFormat) | 
|  | return E_FAIL; | 
|  |  | 
|  | FreeMediaType(outpmt); | 
|  | *outpmt = *amt; | 
|  | outpmt->pUnk = NULL; | 
|  | outpmt->cbFormat = sizeof(WAVEFORMATEXTENSIBLE); | 
|  | outpmt->pbFormat = CoTaskMemAlloc(outpmt->cbFormat); | 
|  |  | 
|  | inwfe = (WAVEFORMATEX*)amt->pbFormat; | 
|  | indepth = inwfe->wBitsPerSample; | 
|  | if (inwfe->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { | 
|  | WAVEFORMATEXTENSIBLE *inwfx = (WAVEFORMATEXTENSIBLE*)inwfe; | 
|  | inisfloat = IsEqualGUID(&inwfx->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT); | 
|  | if (inwfx->Samples.wValidBitsPerSample) | 
|  | indepth = inwfx->Samples.wValidBitsPerSample; | 
|  | } | 
|  |  | 
|  | capsin = gst_caps_new_simple(inisfloat ? "audio/x-raw-float" : "audio/x-raw-int", | 
|  | "endianness", G_TYPE_INT, 1234, | 
|  | "width", G_TYPE_INT, inwfe->wBitsPerSample, | 
|  | "depth", G_TYPE_INT, indepth, | 
|  | "channels", G_TYPE_INT, inwfe->nChannels, | 
|  | "rate", G_TYPE_INT, inwfe->nSamplesPerSec, | 
|  | NULL); | 
|  |  | 
|  | outwfe = (WAVEFORMATEX*)outpmt->pbFormat; | 
|  | outwfx = (WAVEFORMATEXTENSIBLE*)outwfe; | 
|  | outwfe->wFormatTag = WAVE_FORMAT_EXTENSIBLE; | 
|  | outwfe->nChannels = 2; | 
|  | outwfe->nSamplesPerSec = inwfe->nSamplesPerSec; | 
|  | outwfe->wBitsPerSample = 16; | 
|  | outwfe->nBlockAlign = outwfe->nChannels * outwfe->wBitsPerSample / 8; | 
|  | outwfe->nAvgBytesPerSec = outwfe->nBlockAlign * outwfe->nSamplesPerSec; | 
|  | outwfe->cbSize = sizeof(*outwfx) - sizeof(*outwfe); | 
|  | outwfx->Samples.wValidBitsPerSample = outwfe->wBitsPerSample; | 
|  | outwfx->dwChannelMask = SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT; | 
|  | outwfx->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; | 
|  |  | 
|  | capsout = gst_caps_new_simple("audio/x-raw-int", | 
|  | "endianness", G_TYPE_INT, 1234, | 
|  | "width", G_TYPE_INT, outwfe->wBitsPerSample, | 
|  | "depth", G_TYPE_INT, outwfx->Samples.wValidBitsPerSample, | 
|  | "channels", G_TYPE_INT, outwfe->nChannels, | 
|  | "rate", G_TYPE_INT, outwfe->nSamplesPerSec, | 
|  | NULL); | 
|  |  | 
|  | hr = Gstreamer_transform_ConnectInput(This, amt, capsin, capsout); | 
|  | FIXME("%08x\n", hr); | 
|  | gst_caps_unref(capsin); | 
|  | gst_caps_unref(capsout); | 
|  |  | 
|  | This->cbBuffer = inwfe->nAvgBytesPerSec; | 
|  | return hr; | 
|  | } | 
|  |  | 
|  | static const TransformFilterFuncTable Gstreamer_AudioConvert_vtbl = { | 
|  | Gstreamer_transform_DecideBufferSize, | 
|  | Gstreamer_transform_ProcessBegin, | 
|  | Gstreamer_transform_ProcessData, | 
|  | Gstreamer_transform_ProcessEnd, | 
|  | Gstreamer_AudioConvert_QueryConnect, | 
|  | Gstreamer_AudioConvert_SetMediaType, | 
|  | Gstreamer_AudioConvert_ConnectInput, | 
|  | Gstreamer_transform_Cleanup, | 
|  | Gstreamer_transform_EndOfStream, | 
|  | Gstreamer_transform_BeginFlush, | 
|  | Gstreamer_transform_EndFlush, | 
|  | Gstreamer_transform_NewSegment, | 
|  | Gstreamer_transform_QOS | 
|  | }; | 
|  |  | 
|  | IUnknown * CALLBACK Gstreamer_AudioConvert_create(IUnknown *punkout, HRESULT *phr) | 
|  | { | 
|  | IUnknown *obj = NULL; | 
|  | if (!Gstreamer_init()) | 
|  | { | 
|  | *phr = E_FAIL; | 
|  | return NULL; | 
|  | } | 
|  | *phr = Gstreamer_transform_create(punkout, &CLSID_Gstreamer_AudioConvert, "audioconvert", &Gstreamer_AudioConvert_vtbl, (LPVOID*)&obj); | 
|  | return obj; | 
|  | } | 
|  |  | 
|  | HRESULT WINAPI GSTTf_QueryInterface(IBaseFilter * iface, REFIID riid, LPVOID * ppv) | 
|  | { | 
|  | HRESULT hr; | 
|  | GstTfImpl *This = (GstTfImpl*)iface; | 
|  | TRACE("(%p/%p)->(%s, %p)\n", This, iface, debugstr_guid(riid), ppv); | 
|  |  | 
|  | if (IsEqualIID(riid, &IID_IMediaSeeking)) | 
|  | return IUnknown_QueryInterface(This->seekthru_unk, riid, ppv); | 
|  |  | 
|  | hr = TransformFilterImpl_QueryInterface(iface, riid, ppv); | 
|  |  | 
|  | return hr; | 
|  | } | 
|  |  | 
|  | static const IBaseFilterVtbl GSTTf_Vtbl = | 
|  | { | 
|  | GSTTf_QueryInterface, | 
|  | BaseFilterImpl_AddRef, | 
|  | TransformFilterImpl_Release, | 
|  | BaseFilterImpl_GetClassID, | 
|  | TransformFilterImpl_Stop, | 
|  | TransformFilterImpl_Pause, | 
|  | TransformFilterImpl_Run, | 
|  | BaseFilterImpl_GetState, | 
|  | BaseFilterImpl_SetSyncSource, | 
|  | BaseFilterImpl_GetSyncSource, | 
|  | BaseFilterImpl_EnumPins, | 
|  | TransformFilterImpl_FindPin, | 
|  | BaseFilterImpl_QueryFilterInfo, | 
|  | BaseFilterImpl_JoinFilterGraph, | 
|  | BaseFilterImpl_QueryVendorInfo | 
|  | }; |