/* * GStreamer splitter + decoder, adapted from parser.c * * 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 #include #include #include #include "gst_private.h" #include "gst_guids.h" #include "vfwmsgs.h" #include "amvideo.h" #include "wine/unicode.h" #include "wine/debug.h" #include #include "dvdmedia.h" #include "mmreg.h" #include "ks.h" #include "initguid.h" #include "ksmedia.h" WINE_DEFAULT_DEBUG_CHANNEL(gstreamer); typedef struct GSTOutPin GSTOutPin; typedef struct GSTInPin { BasePin pin; IAsyncReader *pReader; IMemAllocator *pAlloc; } GSTInPin; typedef struct GSTImpl { BaseFilter filter; GSTInPin pInputPin; GSTOutPin **ppPins; LONG cStreams; LONGLONG filesize; BOOL discont, initial; GstElement *gstfilter; GstPad *my_src, *their_sink; GstBus *bus; guint64 start, nextofs, nextpullofs, stop; ALLOCATOR_PROPERTIES props; HANDLE event, changed_ofs; HANDLE push_thread; } GSTImpl; struct GSTOutPin { BaseOutputPin pin; IQualityControl IQualityControl_iface; GstPad *their_src; GstPad *my_sink; int isaud, isvid; AM_MEDIA_TYPE * pmt; HANDLE caps_event; GstSegment *segment; SourceSeeking seek; }; static const WCHAR wcsInputPinName[] = {'i','n','p','u','t',' ','p','i','n',0}; static const IMediaSeekingVtbl GST_Seeking_Vtbl; static const IPinVtbl GST_OutputPin_Vtbl; static const IPinVtbl GST_InputPin_Vtbl; static const IBaseFilterVtbl GST_Vtbl; static const IQualityControlVtbl GSTOutPin_QualityControl_Vtbl; static HRESULT GST_AddPin(GSTImpl *This, const PIN_INFO *piOutput, const AM_MEDIA_TYPE *amt); static HRESULT GST_RemoveOutputPins(GSTImpl *This); static HRESULT WINAPI GST_ChangeCurrent(IMediaSeeking *iface); static HRESULT WINAPI GST_ChangeStop(IMediaSeeking *iface); static HRESULT WINAPI GST_ChangeRate(IMediaSeeking *iface); static int amt_from_gst_caps_audio(GstCaps *caps, AM_MEDIA_TYPE *amt) { WAVEFORMATEXTENSIBLE *wfe; WAVEFORMATEX *wfx; GstStructure *arg; gint32 depth = 0, bpp = 0; const char *typename; arg = gst_caps_get_structure(caps, 0); typename = gst_structure_get_name(arg); if (!typename) return 0; wfe = CoTaskMemAlloc(sizeof(*wfe)); wfx = (WAVEFORMATEX*)wfe; amt->majortype = MEDIATYPE_Audio; amt->subtype = MEDIASUBTYPE_PCM; amt->formattype = FORMAT_WaveFormatEx; amt->pbFormat = (BYTE*)wfe; amt->cbFormat = sizeof(*wfe); amt->bFixedSizeSamples = 0; amt->bTemporalCompression = 1; amt->lSampleSize = 0; amt->pUnk = NULL; wfx->wFormatTag = WAVE_FORMAT_EXTENSIBLE; if (!gst_structure_get_int(arg, "channels", (INT*)&wfx->nChannels)) return 0; if (!gst_structure_get_int(arg, "rate", (INT*)&wfx->nSamplesPerSec)) return 0; gst_structure_get_int(arg, "width", &depth); gst_structure_get_int(arg, "depth", &bpp); if (!depth || depth > 32 || depth % 8) depth = bpp; else if (!bpp) bpp = depth; wfe->Samples.wValidBitsPerSample = depth; wfx->wBitsPerSample = bpp; wfx->cbSize = sizeof(*wfe)-sizeof(*wfx); switch (wfx->nChannels) { case 1: wfe->dwChannelMask = KSAUDIO_SPEAKER_MONO; break; case 2: wfe->dwChannelMask = KSAUDIO_SPEAKER_STEREO; break; case 4: wfe->dwChannelMask = KSAUDIO_SPEAKER_SURROUND; break; case 5: wfe->dwChannelMask = (KSAUDIO_SPEAKER_5POINT1 & ~SPEAKER_LOW_FREQUENCY); break; case 6: wfe->dwChannelMask = KSAUDIO_SPEAKER_5POINT1; break; case 8: wfe->dwChannelMask = KSAUDIO_SPEAKER_7POINT1; break; default: wfe->dwChannelMask = 0; } if (!strcmp(typename, "audio/x-raw-float")) { wfe->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; wfx->wBitsPerSample = wfe->Samples.wValidBitsPerSample = 32; } else { wfe->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; if (wfx->nChannels <= 2 && bpp <= 16 && depth == bpp) { wfx->wFormatTag = WAVE_FORMAT_PCM; wfx->cbSize = 0; } } wfx->nBlockAlign = wfx->nChannels * wfx->wBitsPerSample/8; wfx->nAvgBytesPerSec = wfx->nSamplesPerSec * wfx->nBlockAlign; return 1; } static int amt_from_gst_caps_video(GstCaps *caps, AM_MEDIA_TYPE *amt) { VIDEOINFOHEADER *vih = CoTaskMemAlloc(sizeof(*vih)); BITMAPINFOHEADER *bih = &vih->bmiHeader; GstStructure *arg; gint32 width = 0, height = 0, nom = 0, denom = 0; const char *typename; arg = gst_caps_get_structure(caps, 0); typename = gst_structure_get_name(arg); if (!typename) return 0; if (!gst_structure_get_int(arg, "width", &width) || !gst_structure_get_int(arg, "height", &height) || !gst_structure_get_fraction(arg, "framerate", &nom, &denom)) return 0; amt->formattype = FORMAT_VideoInfo; amt->pbFormat = (BYTE*)vih; amt->cbFormat = sizeof(*vih); amt->bFixedSizeSamples = amt->bTemporalCompression = 1; amt->lSampleSize = 0; amt->pUnk = NULL; ZeroMemory(vih, sizeof(*vih)); amt->majortype = MEDIATYPE_Video; if (!strcmp(typename, "video/x-raw-rgb")) { if (!gst_structure_get_int(arg, "bpp", (INT*)&bih->biBitCount)) return 0; switch (bih->biBitCount) { case 16: amt->subtype = MEDIASUBTYPE_RGB555; break; case 24: amt->subtype = MEDIASUBTYPE_RGB24; break; case 32: amt->subtype = MEDIASUBTYPE_RGB32; break; default: FIXME("Unknown bpp %u\n", bih->biBitCount); return 0; } bih->biCompression = BI_RGB; } else { amt->subtype = MEDIATYPE_Video; if (!gst_structure_get_fourcc(arg, "format", &amt->subtype.Data1)) return 0; 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'): bih->biBitCount = 12; break; case mmioFOURCC('Y','U','Y','2'): case mmioFOURCC('Y','V','Y','U'): bih->biBitCount = 16; break; } bih->biCompression = amt->subtype.Data1; } bih->biSizeImage = width * height * bih->biBitCount / 8; vih->AvgTimePerFrame = 10000000; vih->AvgTimePerFrame *= denom; vih->AvgTimePerFrame /= nom; vih->rcSource.left = 0; vih->rcSource.right = width; vih->rcSource.top = height; vih->rcSource.bottom = 0; vih->rcTarget = vih->rcSource; bih->biSize = sizeof(*bih); bih->biWidth = width; bih->biHeight = height; bih->biPlanes = 1; return 1; } static gboolean accept_caps_sink(GstPad *pad, GstCaps *caps) { GSTOutPin *pin = gst_pad_get_element_private(pad); AM_MEDIA_TYPE amt; GstStructure *arg; const char *typename; int ret; arg = gst_caps_get_structure(caps, 0); typename = gst_structure_get_name(arg); if (!strcmp(typename, "audio/x-raw-int") || !strcmp(typename, "audio/x-raw-float")) { if (!pin->isaud) { ERR("Setting audio caps on non-audio pad?\n"); return 0; } ret = amt_from_gst_caps_audio(caps, &amt); FreeMediaType(&amt); TRACE("+%i\n", ret); return ret; } else if (!strcmp(typename, "video/x-raw-rgb") || !strcmp(typename, "video/x-raw-yuv")) { if (!pin->isvid) { ERR("Setting video caps on non-video pad?\n"); return 0; } ret = amt_from_gst_caps_video(caps, &amt); FreeMediaType(&amt); TRACE("-%i\n", ret); return ret; } else { FIXME("Unhandled type \"%s\"\n", typename); return 0; } } static gboolean setcaps_sink(GstPad *pad, GstCaps *caps) { GSTOutPin *pin = gst_pad_get_element_private(pad); GSTImpl *This = (GSTImpl *)pin->pin.pin.pinInfo.pFilter; AM_MEDIA_TYPE amt; GstStructure *arg; const char *typename; int ret; arg = gst_caps_get_structure(caps, 0); typename = gst_structure_get_name(arg); if (!strcmp(typename, "audio/x-raw-int") || !strcmp(typename, "audio/x-raw-float")) { if (!pin->isaud) { ERR("Setting audio caps on non-audio pad?\n"); return 0; } ret = amt_from_gst_caps_audio(caps, &amt); } else if (!strcmp(typename, "video/x-raw-rgb") || !strcmp(typename, "video/x-raw-yuv")) { if (!pin->isvid) { ERR("Setting video caps on non-video pad?\n"); return 0; } ret = amt_from_gst_caps_video(caps, &amt); if (ret) This->props.cbBuffer = max(This->props.cbBuffer, ((VIDEOINFOHEADER*)amt.pbFormat)->bmiHeader.biSizeImage); } else { FIXME("Unhandled type \"%s\"\n", typename); return 0; } TRACE("Linking returned %i for %s\n", ret, typename); if (!ret) return 0; FreeMediaType(pin->pmt); *pin->pmt = amt; return 1; } static gboolean gst_base_src_perform_seek(GSTImpl *This, GstEvent *event) { gboolean res = TRUE; gdouble rate; GstFormat seek_format; GstSeekFlags flags; GstSeekType cur_type, stop_type; gint64 cur, stop; gboolean flush; guint32 seqnum; GstEvent *tevent; BOOL thread = !!This->push_thread; gst_event_parse_seek(event, &rate, &seek_format, &flags, &cur_type, &cur, &stop_type, &stop); if (seek_format != GST_FORMAT_BYTES) { FIXME("Not handling other format %i\n", seek_format); return 0; } flush = flags & GST_SEEK_FLAG_FLUSH; seqnum = gst_event_get_seqnum(event); /* send flush start */ if (flush) { tevent = gst_event_new_flush_start(); gst_event_set_seqnum(tevent, seqnum); gst_pad_push_event(This->my_src, tevent); if (This->pInputPin.pReader) IAsyncReader_BeginFlush(This->pInputPin.pReader); if (thread) gst_pad_activate_push(This->my_src, 0); } This->nextofs = This->start = cur; /* and prepare to continue streaming */ if (flush) { tevent = gst_event_new_flush_stop(); gst_event_set_seqnum(tevent, seqnum); gst_pad_push_event(This->my_src, tevent); if (This->pInputPin.pReader) IAsyncReader_EndFlush(This->pInputPin.pReader); if (thread) gst_pad_activate_push(This->my_src, 1); } return res; } static gboolean event_src(GstPad *pad, GstEvent *event) { GSTImpl *This = gst_pad_get_element_private(pad); switch (event->type) { case GST_EVENT_SEEK: return gst_base_src_perform_seek(This, event); case GST_EVENT_FLUSH_START: EnterCriticalSection(&This->filter.csFilter); if (This->pInputPin.pReader) IAsyncReader_BeginFlush(This->pInputPin.pReader); LeaveCriticalSection(&This->filter.csFilter); break; case GST_EVENT_FLUSH_STOP: EnterCriticalSection(&This->filter.csFilter); if (This->pInputPin.pReader) IAsyncReader_EndFlush(This->pInputPin.pReader); LeaveCriticalSection(&This->filter.csFilter); break; default: FIXME("%p (%u) stub\n", event, event->type); case GST_EVENT_TAG: case GST_EVENT_QOS: return gst_pad_event_default(pad, event); } return 1; } static gboolean event_sink(GstPad *pad, GstEvent *event) { GSTOutPin *pin = gst_pad_get_element_private(pad); switch (event->type) { case GST_EVENT_NEWSEGMENT: { gboolean update; gdouble rate, applied_rate; GstFormat format; gint64 start, stop, pos; gst_event_parse_new_segment_full(event, &update, &rate, &applied_rate, &format, &start, &stop, &pos); if (format != GST_FORMAT_TIME) { FIXME("Ignoring new segment because of format %i\n", format); return 1; } gst_segment_set_newsegment_full(pin->segment, update, rate, applied_rate, format, start, stop, pos); pos /= 100; if (stop > 0) stop /= 100; if (pin->pin.pin.pConnectedTo) IPin_NewSegment(pin->pin.pin.pConnectedTo, pos, stop, rate*applied_rate); return 1; } case GST_EVENT_EOS: if (pin->pin.pin.pConnectedTo) IPin_EndOfStream(pin->pin.pin.pConnectedTo); return 1; case GST_EVENT_FLUSH_START: if (pin->pin.pin.pConnectedTo) IPin_BeginFlush(pin->pin.pin.pConnectedTo); return 1; case GST_EVENT_FLUSH_STOP: gst_segment_init(pin->segment, GST_FORMAT_TIME); if (pin->pin.pin.pConnectedTo) IPin_EndFlush(pin->pin.pin.pConnectedTo); return 1; default: FIXME("%p stub %s\n", event, gst_event_type_get_name(event->type)); return gst_pad_event_default(pad, event); } } static void release_sample(void *data) { ULONG ret; ret = IMediaSample_Release((IMediaSample *)data); TRACE("Releasing %p returns %u\n", data, ret); } static DWORD CALLBACK push_data(LPVOID iface) { LONGLONG maxlen, curlen; GSTImpl *This = iface; IMediaSample *buf; DWORD_PTR user; HRESULT hr; if (!This->stop) IAsyncReader_Length(This->pInputPin.pReader, &maxlen, &curlen); else maxlen = This->stop; TRACE("Starting..\n"); for (;;) { REFERENCE_TIME tStart, tStop; ULONG len; GstBuffer *gstbuf; BYTE *data; int ret; hr = IMemAllocator_GetBuffer(This->pInputPin.pAlloc, &buf, NULL, NULL, 0); if (FAILED(hr)) break; if (This->nextofs >= maxlen) break; len = IMediaSample_GetSize(buf); if (This->nextofs + len > maxlen) len = maxlen - This->nextofs; tStart = MEDIATIME_FROM_BYTES(This->nextofs); tStop = tStart + MEDIATIME_FROM_BYTES(len); IMediaSample_SetTime(buf, &tStart, &tStop); hr = IAsyncReader_Request(This->pInputPin.pReader, buf, 0); if (FAILED(hr)) { IMediaSample_Release(buf); break; } This->nextofs += len; hr = IAsyncReader_WaitForNext(This->pInputPin.pReader, -1, &buf, &user); if (FAILED(hr) || !buf) { if (buf) IMediaSample_Release(buf); break; } IMediaSample_GetPointer(buf, &data); gstbuf = gst_app_buffer_new(data, IMediaSample_GetActualDataLength(buf), release_sample, buf); if (!gstbuf) { IMediaSample_Release(buf); break; } gstbuf->duration = gstbuf->timestamp = -1; ret = gst_pad_push(This->my_src, gstbuf); if (ret >= 0) hr = S_OK; else ERR("Sending returned: %i\n", ret); if (ret == GST_FLOW_ERROR) hr = E_FAIL; else if (ret == GST_FLOW_WRONG_STATE) hr = VFW_E_WRONG_STATE; else if (ret == GST_FLOW_RESEND) hr = S_FALSE; if (hr != S_OK) break; } gst_pad_push_event(This->my_src, gst_event_new_eos()); TRACE("Almost stopping.. %08x\n", hr); do { IAsyncReader_WaitForNext(This->pInputPin.pReader, 0, &buf, &user); if (buf) IMediaSample_Release(buf); } while (buf); TRACE("Stopping.. %08x\n", hr); return 0; } static HRESULT WINAPI GST_OutPin_QueryAccept(IPin *iface, const AM_MEDIA_TYPE *pmt) { GSTOutPin *pin = (GSTOutPin*)iface; FIXME("stub %p\n", pin); return S_OK; } static GstFlowReturn got_data_sink(GstPad *pad, GstBuffer *buf) { GSTOutPin *pin = gst_pad_get_element_private(pad); GSTImpl *This = (GSTImpl *)pin->pin.pin.pinInfo.pFilter; IMediaSample *sample; HRESULT hr; BOOL freeSamp = FALSE; if (This->initial) { gst_buffer_unref(buf); TRACE("Triggering %p %p\n", pad, pin->caps_event); SetEvent(pin->caps_event); return GST_FLOW_NOT_LINKED; } if (GST_IS_APP_BUFFER(buf)) { sample = GST_APP_BUFFER(buf)->priv; TRACE("Pushing buffer\n"); } else if (buf->parent && GST_IS_APP_BUFFER(buf->parent)) { sample = GST_APP_BUFFER(buf->parent)->priv; TRACE("Pushing sub-buffer\n"); } else { BYTE *ptr = NULL; hr = BaseOutputPinImpl_GetDeliveryBuffer(&pin->pin, &sample, NULL, NULL, 0); freeSamp = TRUE; if (hr == VFW_E_NOT_CONNECTED) { gst_buffer_unref(buf); return GST_FLOW_NOT_LINKED; } if (FAILED(hr)) { gst_buffer_unref(buf); ERR("Didn't get a GST_APP_BUFFER, and could not get a delivery buffer (%x), returning GST_FLOW_WRONG_STATE\n", hr); return GST_FLOW_WRONG_STATE; } TRACE("Did not get a GST_APP_BUFFER, creating a sample\n"); IMediaSample_GetPointer(sample, &ptr); memcpy(ptr, GST_BUFFER_DATA(buf), GST_BUFFER_SIZE(buf)); } IMediaSample_SetActualDataLength(sample, GST_BUFFER_SIZE(buf)); if (GST_BUFFER_TIMESTAMP_IS_VALID(buf)) { REFERENCE_TIME rtStart = gst_segment_to_running_time(pin->segment, GST_FORMAT_TIME, buf->timestamp); if (rtStart >= 0) rtStart /= 100; if (GST_BUFFER_DURATION_IS_VALID(buf)) { REFERENCE_TIME tStart = buf->timestamp / 100; REFERENCE_TIME tStop = (buf->timestamp + buf->duration) / 100; REFERENCE_TIME rtStop; rtStop = gst_segment_to_running_time(pin->segment, GST_FORMAT_TIME, buf->timestamp + buf->duration); if (rtStop >= 0) rtStop /= 100; TRACE("Current time on %p: %i to %i ms\n", pin, (int)(rtStart / 10000), (int)(rtStop / 10000)); IMediaSample_SetTime(sample, &rtStart, rtStop >= 0 ? &rtStop : NULL); IMediaSample_SetMediaTime(sample, &tStart, &tStop); } else { IMediaSample_SetTime(sample, rtStart >= 0 ? &rtStart : NULL, NULL); IMediaSample_SetMediaTime(sample, NULL, NULL); } } else { IMediaSample_SetTime(sample, NULL, NULL); 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)); if (!pin->pin.pin.pConnectedTo) hr = VFW_E_NOT_CONNECTED; else hr = IMemInputPin_Receive(pin->pin.pMemInputPin, sample); TRACE("sending sample: %08x\n", hr); gst_buffer_unref(buf); if (freeSamp) IMediaSample_Release(sample); if (hr == VFW_E_NOT_CONNECTED) return GST_FLOW_NOT_LINKED; else if (FAILED(hr)) return GST_FLOW_WRONG_STATE; if (hr != S_OK) return GST_FLOW_RESEND; return GST_FLOW_OK; } static GstFlowReturn request_buffer_sink(GstPad *pad, guint64 ofs, guint size, GstCaps *caps, GstBuffer **buf) { GSTOutPin *pin = gst_pad_get_element_private(pad); GSTImpl *This = (GSTImpl *)pin->pin.pin.pinInfo.pFilter; IMediaSample *sample; BYTE *ptr; HRESULT hr; TRACE("Requesting buffer\n"); if (This->initial) { int ret; ret = setcaps_sink(pad, caps); if (!ret) return GST_FLOW_NOT_NEGOTIATED; *buf = gst_buffer_new_and_alloc(size); return GST_FLOW_OK; } if (caps && caps != GST_PAD_CAPS(pad)) if (!setcaps_sink(pad, caps)) return GST_FLOW_NOT_NEGOTIATED; hr = BaseOutputPinImpl_GetDeliveryBuffer(&pin->pin, &sample, NULL, NULL, 0); if (hr == VFW_E_NOT_CONNECTED) return GST_FLOW_NOT_LINKED; if (FAILED(hr)) { ERR("Could not get output buffer: %08x\n", hr); *buf = NULL; 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; } gst_buffer_set_caps(*buf, caps); return GST_FLOW_OK; } static GstFlowReturn request_buffer_src(GstPad *pad, guint64 ofs, guint len, GstBuffer **buf) { GSTImpl *This = gst_pad_get_element_private(pad); int ret; *buf = NULL; TRACE("Requesting %s %u\n", wine_dbgstr_longlong(ofs), len); if (ofs == (guint64)-1) ofs = This->nextpullofs; if (ofs >= This->filesize) { WARN("Reading past eof: %s, %u\n", wine_dbgstr_longlong(ofs), len); return GST_FLOW_UNEXPECTED; } if (len + ofs > This->filesize) len = This->filesize - ofs; This->nextpullofs = ofs + len; ret = gst_pad_alloc_buffer(This->my_src, ofs, len, NULL, buf); if (ret >= 0) { HRESULT hr; hr = IAsyncReader_SyncRead(This->pInputPin.pReader, ofs, len, GST_BUFFER_DATA(*buf)); if (FAILED(hr)) { ERR("Returned %08x\n", hr); return GST_FLOW_ERROR; } } return ret; } static DWORD CALLBACK push_data_init(LPVOID iface) { GSTImpl *This = iface; DWORD64 ofs = 0; TRACE("Starting..\n"); for (;;) { GstBuffer *buf; GstFlowReturn ret = request_buffer_src(This->my_src, ofs, 4096, &buf); if (ret < 0) { ERR("Obtaining buffer returned: %i\n", ret); break; } ret = gst_pad_push(This->my_src, buf); ofs += 4096; if (ret) TRACE("Sending returned: %i\n", ret); if (ret < 0) break; } TRACE("Stopping..\n"); return 0; } static void removed_decoded_pad(GstElement *bin, GstPad *pad, GSTImpl *This) { int x; GSTOutPin *pin; EnterCriticalSection(&This->filter.csFilter); for (x = 0; x < This->cStreams; ++x) { if (This->ppPins[x]->their_src == pad) break; } if (x == This->cStreams) goto out; pin = This->ppPins[x]; gst_pad_unlink(pin->their_src, pin->my_sink); gst_object_unref(pin->their_src); pin->their_src = NULL; out: TRACE("Removed %i/%i\n", x, This->cStreams); LeaveCriticalSection(&This->filter.csFilter); } static void init_new_decoded_pad(GstElement *bin, GstPad *pad, gboolean last, GSTImpl *This) { HRESULT hr; PIN_INFO piOutput; const char *typename; char *name; AM_MEDIA_TYPE amt = { }; GstCaps *caps; GstStructure *arg; GstPad *mypad; GSTOutPin *pin; int ret; int isvid = 0, isaud = 0; piOutput.dir = PINDIR_OUTPUT; piOutput.pFilter = (IBaseFilter *)This; name = gst_pad_get_name(pad); MultiByteToWideChar(CP_UNIXCP, 0, name, -1, piOutput.achName, sizeof(piOutput.achName) / sizeof(piOutput.achName[0]) - 1); TRACE("Name: %s\n", name); g_free(name); piOutput.achName[sizeof(piOutput.achName) / sizeof(piOutput.achName[0]) - 1] = 0; caps = gst_pad_get_caps_reffed(pad); arg = gst_caps_get_structure(caps, 0); typename = gst_structure_get_name(arg); mypad = gst_pad_new(NULL, GST_PAD_SINK); gst_pad_set_chain_function(mypad, got_data_sink); gst_pad_set_event_function(mypad, event_sink); gst_pad_set_bufferalloc_function(mypad, request_buffer_sink); gst_pad_set_acceptcaps_function(mypad, accept_caps_sink); gst_pad_set_setcaps_function(mypad, setcaps_sink); if (!strcmp(typename, "audio/x-raw-int") || !strcmp(typename, "audio/x-raw-float")) { isaud = 1; } else if (!strcmp(typename, "video/x-raw-rgb") || !strcmp(typename, "video/x-raw-yuv")) { isvid = 1; } else { FIXME("Unknown type \'%s\'\n", typename); return; } GST_PAD_CAPS(mypad) = GST_CAPS_ANY; hr = GST_AddPin(This, &piOutput, &amt); if (FAILED(hr)) { ERR("%08x\n", hr); return; } pin = This->ppPins[This->cStreams - 1]; gst_pad_set_element_private(mypad, pin); pin->my_sink = mypad; pin->isaud = isaud; pin->isvid = isvid; gst_segment_init(pin->segment, GST_FORMAT_TIME); ret = gst_pad_link(pad, mypad); gst_pad_activate_push(mypad, 1); TRACE("Linking: %i\n", ret); if (ret >= 0) { pin->their_src = pad; gst_object_ref(pin->their_src); } } static void existing_new_pad(GstElement *bin, GstPad *pad, gboolean last, GSTImpl *This) { int x; if (gst_pad_is_linked(pad)) return; /* Still holding our own lock */ if (This->initial) { init_new_decoded_pad(bin, pad, last, This); return; } EnterCriticalSection(&This->filter.csFilter); for (x = 0; x < This->cStreams; ++x) { GSTOutPin *pin = This->ppPins[x]; if (!pin->their_src) { gst_segment_init(pin->segment, GST_FORMAT_TIME); if (gst_pad_link(pad, pin->my_sink) >= 0) { pin->their_src = pad; gst_object_ref(pin->their_src); TRACE("Relinked\n"); LeaveCriticalSection(&This->filter.csFilter); return; } } } init_new_decoded_pad(bin, pad, last, This); LeaveCriticalSection(&This->filter.csFilter); } static gboolean check_get_range(GstPad *pad) { return 1; } static gboolean query_function(GstPad *pad, GstQuery *query) { GSTImpl *This = gst_pad_get_element_private(pad); GstFormat format; int ret; LONGLONG duration; switch (GST_QUERY_TYPE(query)) { case GST_QUERY_DURATION: gst_query_parse_duration (query, &format, NULL); if (format == GST_FORMAT_PERCENT) { gst_query_set_duration (query, GST_FORMAT_PERCENT, GST_FORMAT_PERCENT_MAX); return 1; } ret = gst_pad_query_convert (pad, GST_FORMAT_BYTES, This->filesize, &format, &duration); gst_query_set_duration(query, format, duration); return ret; case GST_QUERY_SEEKING: gst_query_parse_seeking (query, &format, NULL, NULL, NULL); TRACE("Seeking %i %i\n", format, GST_FORMAT_BYTES); if (format != GST_FORMAT_BYTES) return 0; gst_query_set_seeking(query, GST_FORMAT_BYTES, 1, 0, This->filesize); return 1; default: FIXME("Unhandled query type %i\n", GST_QUERY_TYPE(query)); case GST_QUERY_URI: case GST_QUERY_CONVERT: return 0; } } static gboolean activate_push(GstPad *pad, gboolean activate) { GSTImpl *This = gst_pad_get_element_private(pad); EnterCriticalSection(&This->filter.csFilter); if (!activate) { TRACE("Deactivating\n"); if (!This->initial) IAsyncReader_BeginFlush(This->pInputPin.pReader); if (This->push_thread) { WaitForSingleObject(This->push_thread, -1); CloseHandle(This->push_thread); This->push_thread = NULL; } if (!This->initial) IAsyncReader_EndFlush(This->pInputPin.pReader); if (This->filter.state == State_Stopped) This->nextofs = This->start; } else if (!This->push_thread) { TRACE("Activating\n"); if (This->initial) This->push_thread = CreateThread(NULL, 0, push_data_init, This, 0, NULL); else This->push_thread = CreateThread(NULL, 0, push_data, This, 0, NULL); } LeaveCriticalSection(&This->filter.csFilter); return 1; } static void no_more_pads(GstElement *decodebin, GSTImpl *This) { TRACE("Done\n"); SetEvent(This->event); } typedef enum { GST_AUTOPLUG_SELECT_TRY, GST_AUTOPLUG_SELECT_EXPOSE, GST_AUTOPLUG_SELECT_SKIP } GstAutoplugSelectResult; static GstAutoplugSelectResult autoplug_blacklist(GstElement *bin, GstPad *pad, GstCaps *caps, GstElementFactory *fact, GSTImpl *This) { const char *name = gst_element_factory_get_longname(fact); if (strstr(name, "Player protection")) { WARN("Blacklisted a/52 decoder because it only works in Totem\n"); return GST_AUTOPLUG_SELECT_SKIP; } if (!strcmp(name, "Fluendo Hardware Accelerated Video Decoder")) { WARN("Disabled video acceleration since it breaks in wine\n"); return GST_AUTOPLUG_SELECT_SKIP; } TRACE("using \"%s\"\n", name); return GST_AUTOPLUG_SELECT_TRY; } static GstBusSyncReply watch_bus(GstBus *bus, GstMessage *msg, gpointer data) { GSTImpl *This = data; GError *err = NULL; gchar *dbg_info = NULL; if (GST_MESSAGE_TYPE(msg) & GST_MESSAGE_ERROR) { gst_message_parse_error(msg, &err, &dbg_info); FIXME("%s: %s\n", GST_OBJECT_NAME(msg->src), err->message); WARN("%s\n", dbg_info); SetEvent(This->event); } else if (GST_MESSAGE_TYPE(msg) & GST_MESSAGE_WARNING) { gst_message_parse_warning(msg, &err, &dbg_info); WARN("%s: %s\n", GST_OBJECT_NAME(msg->src), err->message); WARN("%s\n", dbg_info); } if (err) g_error_free(err); g_free(dbg_info); return GST_BUS_DROP; } static void unknown_type(GstElement *bin, GstPad *pad, GstCaps *caps, GSTImpl *This) { gchar *strcaps = gst_caps_to_string(caps); FIXME("Could not find a filter for caps: %s\n", strcaps); g_free(strcaps); } static HRESULT GST_Connect(GSTInPin *pPin, IPin *pConnectPin, ALLOCATOR_PROPERTIES *props) { GSTImpl *This = (GSTImpl*)pPin->pin.pinInfo.pFilter; HRESULT hr; int ret, i; LONGLONG avail, duration; GstFormat format = GST_FORMAT_TIME; GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE( "quartz_src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY); TRACE("%p %p %p\n", pPin, pConnectPin, props); This->props = *props; IAsyncReader_Length(pPin->pReader, &This->filesize, &avail); if (!This->bus) { This->bus = gst_bus_new(); gst_bus_set_sync_handler(This->bus, watch_bus, This); } This->gstfilter = gst_element_factory_make("decodebin2", NULL); if (!This->gstfilter) { FIXME("Could not make source filter, are gstreamer-plugins-* installed for %u bits?\n", 8 * (int)sizeof(void*)); return E_FAIL; } gst_element_set_bus(This->gstfilter, This->bus); g_signal_connect(This->gstfilter, "new-decoded-pad", G_CALLBACK(existing_new_pad), This); g_signal_connect(This->gstfilter, "pad-removed", G_CALLBACK(removed_decoded_pad), This); g_signal_connect(This->gstfilter, "autoplug-select", G_CALLBACK(autoplug_blacklist), This); g_signal_connect(This->gstfilter, "unknown-type", G_CALLBACK(unknown_type), This); This->my_src = gst_pad_new_from_static_template(&src_template, "quartz-src"); gst_pad_set_getrange_function(This->my_src, request_buffer_src); gst_pad_set_checkgetrange_function(This->my_src, check_get_range); gst_pad_set_query_function(This->my_src, query_function); gst_pad_set_activatepush_function(This->my_src, activate_push); gst_pad_set_event_function(This->my_src, event_src); gst_pad_set_element_private (This->my_src, This); This->their_sink = gst_element_get_static_pad(This->gstfilter, "sink"); g_signal_connect(This->gstfilter, "no-more-pads", G_CALLBACK(no_more_pads), This); ret = gst_pad_link(This->my_src, This->their_sink); gst_object_unref(This->their_sink); if (ret < 0) { ERR("Returns: %i\n", ret); return E_FAIL; } This->start = This->nextofs = This->nextpullofs = This->stop = 0; /* Add initial pins */ This->initial = This->discont = 1; ResetEvent(This->event); gst_element_set_state(This->gstfilter, GST_STATE_PLAYING); gst_pad_set_active(This->my_src, 1); WaitForSingleObject(This->event, -1); gst_element_get_state(This->gstfilter, NULL, NULL, -1); if (ret < 0) { WARN("Ret: %i\n", ret); hr = E_FAIL; } else if (!This->cStreams) { FIXME("GStreamer could not find any streams\n"); hr = E_FAIL; } else { gst_pad_query_duration(This->ppPins[0]->their_src, &format, &duration); for (i = 0; i < This->cStreams; ++i) { This->ppPins[i]->seek.llDuration = This->ppPins[i]->seek.llStop = duration / 100; This->ppPins[i]->seek.llCurrent = 0; if (!This->ppPins[i]->seek.llDuration) This->ppPins[i]->seek.dwCapabilities = 0; WaitForSingleObject(This->ppPins[i]->caps_event, -1); } hr = S_OK; } *props = This->props; gst_element_set_state(This->gstfilter, GST_STATE_READY); gst_element_get_state(This->gstfilter, NULL, NULL, -1); if (This->push_thread) gst_pad_activate_push(This->my_src, 0); This->initial = 0; This->nextofs = This->nextpullofs = 0; return hr; } static inline GSTOutPin *impl_from_IMediaSeeking( IMediaSeeking *iface ) { return CONTAINING_RECORD(iface, GSTOutPin, seek.IMediaSeeking_iface); } static IPin* WINAPI GST_GetPin(BaseFilter *iface, int pos) { GSTImpl *This = (GSTImpl *)iface; TRACE("Asking for pos %x\n", pos); if (pos > This->cStreams || pos < 0) return NULL; if (!pos) { IPin_AddRef((IPin*)&This->pInputPin); return (IPin*)&This->pInputPin; } else { IPin_AddRef((IPin*)This->ppPins[pos - 1]); return (IPin*)This->ppPins[pos - 1]; } } static LONG WINAPI GST_GetPinCount(BaseFilter *iface) { GSTImpl *This = (GSTImpl *)iface; return (This->cStreams + 1); } static const BaseFilterFuncTable BaseFuncTable = { GST_GetPin, GST_GetPinCount }; IUnknown * CALLBACK Gstreamer_Splitter_create(IUnknown *punkout, HRESULT *phr) { IUnknown *obj = NULL; PIN_INFO *piInput; GSTImpl *This; if (!Gstreamer_init()) { *phr = E_FAIL; return NULL; } This = CoTaskMemAlloc(sizeof(*This)); obj = (IUnknown*)This; if (!This) { *phr = E_OUTOFMEMORY; return NULL; } BaseFilter_Init(&This->filter, &GST_Vtbl, &CLSID_Gstreamer_Splitter, (DWORD_PTR)(__FILE__ ": GSTImpl.csFilter"), &BaseFuncTable); This->cStreams = 0; This->ppPins = NULL; This->push_thread = NULL; This->event = CreateEventW(NULL, 0, 0, NULL); This->bus = NULL; piInput = &This->pInputPin.pin.pinInfo; piInput->dir = PINDIR_INPUT; piInput->pFilter = (IBaseFilter *)This; lstrcpynW(piInput->achName, wcsInputPinName, sizeof(piInput->achName) / sizeof(piInput->achName[0])); This->pInputPin.pin.IPin_iface.lpVtbl = &GST_InputPin_Vtbl; This->pInputPin.pin.refCount = 1; This->pInputPin.pin.pConnectedTo = NULL; This->pInputPin.pin.pCritSec = &This->filter.csFilter; ZeroMemory(&This->pInputPin.pin.mtCurrent, sizeof(AM_MEDIA_TYPE)); *phr = S_OK; return obj; } static void GST_Destroy(GSTImpl *This) { IPin *connected = NULL; ULONG pinref; TRACE("Destroying\n"); CloseHandle(This->event); /* Don't need to clean up output pins, disconnecting input pin will do that */ IPin_ConnectedTo((IPin *)&This->pInputPin, &connected); if (connected) { assert(IPin_Disconnect(connected) == S_OK); IPin_Release(connected); assert(IPin_Disconnect((IPin *)&This->pInputPin) == S_OK); } pinref = IPin_Release((IPin *)&This->pInputPin); if (pinref) { /* Valgrind could find this, if I kill it here */ ERR("pinref should be null, is %u, destroying anyway\n", pinref); assert((LONG)pinref > 0); while (pinref) pinref = IPin_Release((IPin *)&This->pInputPin); } if (This->bus) { gst_bus_set_sync_handler(This->bus, NULL, NULL); gst_object_unref(This->bus); } CoTaskMemFree(This); } static HRESULT WINAPI GST_QueryInterface(IBaseFilter *iface, REFIID riid, LPVOID *ppv) { GSTImpl *This = (GSTImpl *)iface; TRACE("(%s, %p)\n", debugstr_guid(riid), ppv); *ppv = NULL; if (IsEqualIID(riid, &IID_IUnknown)) *ppv = This; else if (IsEqualIID(riid, &IID_IPersist)) *ppv = This; else if (IsEqualIID(riid, &IID_IMediaFilter)) *ppv = This; else if (IsEqualIID(riid, &IID_IBaseFilter)) *ppv = This; if (*ppv) { IUnknown_AddRef((IUnknown *)(*ppv)); return S_OK; } if (!IsEqualIID(riid, &IID_IPin) && !IsEqualIID(riid, &IID_IVideoWindow) && !IsEqualIID(riid, &IID_IAMFilterMiscFlags)) FIXME("No interface for %s!\n", debugstr_guid(riid)); return E_NOINTERFACE; } static ULONG WINAPI GST_Release(IBaseFilter *iface) { GSTImpl *This = (GSTImpl *)iface; ULONG refCount = BaseFilterImpl_Release(iface); TRACE("(%p)->() Release from %d\n", This, refCount + 1); if (!refCount) GST_Destroy(This); return refCount; } static HRESULT WINAPI GST_Stop(IBaseFilter *iface) { GSTImpl *This = (GSTImpl *)iface; TRACE("()\n"); if (This->gstfilter) gst_element_set_state(This->gstfilter, GST_STATE_READY); return S_OK; } static HRESULT WINAPI GST_Pause(IBaseFilter *iface) { HRESULT hr = S_OK; GSTImpl *This = (GSTImpl *)iface; GstState now; GstStateChangeReturn ret; TRACE("()\n"); if (!This->gstfilter) return VFW_E_NOT_CONNECTED; gst_element_get_state(This->gstfilter, &now, NULL, -1); if (now == GST_STATE_PAUSED) return S_OK; if (now != GST_STATE_PLAYING) hr = IBaseFilter_Run(iface, -1); if (FAILED(hr)) return hr; ret = gst_element_set_state(This->gstfilter, GST_STATE_PAUSED); if (ret == GST_STATE_CHANGE_ASYNC) hr = S_FALSE; return hr; } static HRESULT WINAPI GST_Run(IBaseFilter *iface, REFERENCE_TIME tStart) { HRESULT hr = S_OK; GSTImpl *This = (GSTImpl *)iface; ULONG i; GstState now; HRESULT hr_any = VFW_E_NOT_CONNECTED; TRACE("(%s)\n", wine_dbgstr_longlong(tStart)); if (!This->gstfilter) return VFW_E_NOT_CONNECTED; EnterCriticalSection(&This->filter.csFilter); This->filter.rtStreamStart = tStart; LeaveCriticalSection(&This->filter.csFilter); gst_element_get_state(This->gstfilter, &now, NULL, -1); if (now == GST_STATE_PLAYING) return S_OK; if (now == GST_STATE_PAUSED) { GstStateChangeReturn ret; ret = gst_element_set_state(This->gstfilter, GST_STATE_PLAYING); if (ret == GST_STATE_CHANGE_ASYNC) return S_FALSE; return S_OK; } EnterCriticalSection(&This->filter.csFilter); gst_pad_set_blocked(This->my_src, 0); gst_pad_set_blocked(This->their_sink, 0); gst_element_set_state(This->gstfilter, GST_STATE_PLAYING); This->filter.rtStreamStart = tStart; for (i = 0; i < This->cStreams; i++) { hr = BaseOutputPinImpl_Active((BaseOutputPin *)This->ppPins[i]); if (SUCCEEDED(hr)) { gst_pad_set_blocked(This->ppPins[i]->my_sink, 0); if (This->ppPins[i]->their_src) gst_pad_set_blocked(This->ppPins[i]->their_src, 0); hr_any = hr; } } hr = hr_any; if (SUCCEEDED(hr)) gst_pad_set_active(This->my_src, 1); LeaveCriticalSection(&This->filter.csFilter); return hr; } static HRESULT WINAPI GST_GetState(IBaseFilter *iface, DWORD dwMilliSecsTimeout, FILTER_STATE *pState) { GSTImpl *This = (GSTImpl *)iface; HRESULT hr = S_OK; GstState now, pending; GstStateChangeReturn ret; TRACE("(%d, %p)\n", dwMilliSecsTimeout, pState); if (!This->gstfilter) { *pState = State_Stopped; return S_OK; } ret = gst_element_get_state(This->gstfilter, &now, &pending, dwMilliSecsTimeout == INFINITE ? -1 : dwMilliSecsTimeout * 1000); if (ret == GST_STATE_CHANGE_ASYNC) hr = VFW_S_STATE_INTERMEDIATE; else pending = now; switch (pending) { case GST_STATE_PAUSED: *pState = State_Paused; return hr; case GST_STATE_PLAYING: *pState = State_Running; return hr; default: *pState = State_Stopped; return hr; } } static HRESULT WINAPI GST_FindPin(IBaseFilter *iface, LPCWSTR Id, IPin **ppPin) { FIXME("(%p)->(%s,%p) stub\n", iface, debugstr_w(Id), ppPin); return E_NOTIMPL; } static const IBaseFilterVtbl GST_Vtbl = { GST_QueryInterface, BaseFilterImpl_AddRef, GST_Release, BaseFilterImpl_GetClassID, GST_Stop, GST_Pause, GST_Run, GST_GetState, BaseFilterImpl_SetSyncSource, BaseFilterImpl_GetSyncSource, BaseFilterImpl_EnumPins, GST_FindPin, BaseFilterImpl_QueryFilterInfo, BaseFilterImpl_JoinFilterGraph, BaseFilterImpl_QueryVendorInfo }; static HRESULT WINAPI GST_ChangeCurrent(IMediaSeeking *iface) { return S_OK; } static HRESULT WINAPI GST_ChangeStop(IMediaSeeking *iface) { return S_OK; } static HRESULT WINAPI GST_ChangeRate(IMediaSeeking *iface) { GSTOutPin *This = impl_from_IMediaSeeking(iface); GstEvent *ev = gst_event_new_seek(This->seek.dRate, GST_FORMAT_TIME, 0, GST_SEEK_TYPE_NONE, -1, GST_SEEK_TYPE_NONE, -1); TRACE("(%p) New rate %g\n", iface, This->seek.dRate); gst_pad_push_event(This->my_sink, ev); return S_OK; } static HRESULT WINAPI GST_Seeking_QueryInterface(IMediaSeeking *iface, REFIID riid, void **ppv) { GSTOutPin *This = impl_from_IMediaSeeking(iface); return IUnknown_QueryInterface((IUnknown *)This, riid, ppv); } static ULONG WINAPI GST_Seeking_AddRef(IMediaSeeking *iface) { GSTOutPin *This = impl_from_IMediaSeeking(iface); return IUnknown_AddRef((IUnknown *)This); } static ULONG WINAPI GST_Seeking_Release(IMediaSeeking *iface) { GSTOutPin *This = impl_from_IMediaSeeking(iface); return IUnknown_Release((IUnknown *)This); } static HRESULT WINAPI GST_Seeking_GetCurrentPosition(IMediaSeeking *iface, REFERENCE_TIME *pos) { GSTOutPin *This = impl_from_IMediaSeeking(iface); GstFormat format = GST_FORMAT_TIME; if (!pos) return E_POINTER; if (!This->their_src) { *pos = This->seek.llCurrent; TRACE("Cached value\n"); if (This->seek.llDuration) return S_OK; else return E_NOTIMPL; } if (!gst_pad_query_position(This->their_src, &format, pos)) { WARN("Could not query position\n"); return E_NOTIMPL; } *pos /= 100; This->seek.llCurrent = *pos; return S_OK; } static GstSeekType type_from_flags(DWORD flags) { switch (flags & AM_SEEKING_PositioningBitsMask) { case AM_SEEKING_NoPositioning: return GST_SEEK_TYPE_NONE; case AM_SEEKING_AbsolutePositioning: return GST_SEEK_TYPE_SET; case AM_SEEKING_RelativePositioning: return GST_SEEK_TYPE_CUR; case AM_SEEKING_IncrementalPositioning: return GST_SEEK_TYPE_END; } return GST_SEEK_TYPE_NONE; } static HRESULT WINAPI GST_Seeking_SetPositions(IMediaSeeking *iface, REFERENCE_TIME *pCur, DWORD curflags, REFERENCE_TIME *pStop, DWORD stopflags) { HRESULT hr; GSTOutPin *This = impl_from_IMediaSeeking(iface); GstSeekFlags f = 0; GstSeekType curtype, stoptype; GstEvent *e; if (!This->seek.llDuration) return E_NOTIMPL; hr = SourceSeekingImpl_SetPositions(iface, pCur, curflags, pStop, stopflags); if (!This->their_src) return hr; curtype = type_from_flags(curflags); stoptype = type_from_flags(stopflags); if (curflags & AM_SEEKING_SeekToKeyFrame) f |= GST_SEEK_FLAG_KEY_UNIT; if (curflags & AM_SEEKING_Segment) f |= GST_SEEK_FLAG_SEGMENT; if (!(curflags & AM_SEEKING_NoFlush)) f |= GST_SEEK_FLAG_FLUSH; e = gst_event_new_seek(This->seek.dRate, GST_FORMAT_TIME, f, curtype, pCur ? *pCur * 100 : -1, stoptype, pStop ? *pStop * 100 : -1); if (gst_pad_push_event(This->my_sink, e)) return S_OK; else return E_NOTIMPL; } static const IMediaSeekingVtbl GST_Seeking_Vtbl = { GST_Seeking_QueryInterface, GST_Seeking_AddRef, GST_Seeking_Release, SourceSeekingImpl_GetCapabilities, SourceSeekingImpl_CheckCapabilities, SourceSeekingImpl_IsFormatSupported, SourceSeekingImpl_QueryPreferredFormat, SourceSeekingImpl_GetTimeFormat, SourceSeekingImpl_IsUsingTimeFormat, SourceSeekingImpl_SetTimeFormat, SourceSeekingImpl_GetDuration, SourceSeekingImpl_GetStopPosition, GST_Seeking_GetCurrentPosition, SourceSeekingImpl_ConvertTimeFormat, GST_Seeking_SetPositions, SourceSeekingImpl_GetPositions, SourceSeekingImpl_GetAvailable, SourceSeekingImpl_SetRate, SourceSeekingImpl_GetRate, SourceSeekingImpl_GetPreroll }; static inline GSTOutPin *impl_from_IQualityControl( IQualityControl *iface ) { return (GSTOutPin*)CONTAINING_RECORD(iface, GSTOutPin, IQualityControl_iface); } static HRESULT WINAPI GST_QualityControl_QueryInterface(IQualityControl *iface, REFIID riid, void **ppv) { GSTOutPin *pin = impl_from_IQualityControl(iface); return IPin_QueryInterface((IPin*)pin, riid, ppv); } static ULONG WINAPI GST_QualityControl_AddRef(IQualityControl *iface) { GSTOutPin *pin = impl_from_IQualityControl(iface); return IPin_AddRef((IPin*)pin); } static ULONG WINAPI GST_QualityControl_Release(IQualityControl *iface) { GSTOutPin *pin = impl_from_IQualityControl(iface); return IPin_Release((IPin*)pin); } static HRESULT WINAPI GST_QualityControl_Notify(IQualityControl *iface, IBaseFilter *sender, Quality qm) { GSTOutPin *pin = impl_from_IQualityControl(iface); REFERENCE_TIME late = qm.Late; if (qm.Late < 0 && -qm.Late > qm.TimeStamp) late = -qm.TimeStamp; gst_pad_push_event(pin->my_sink, gst_event_new_qos(1000./qm.Proportion, late*100, qm.TimeStamp*100)); return S_OK; } static HRESULT WINAPI GST_QualityControl_SetSink(IQualityControl *iface, IQualityControl *tonotify) { /* Do nothing */ return S_OK; } static const IQualityControlVtbl GSTOutPin_QualityControl_Vtbl = { GST_QualityControl_QueryInterface, GST_QualityControl_AddRef, GST_QualityControl_Release, GST_QualityControl_Notify, GST_QualityControl_SetSink }; static HRESULT WINAPI GSTOutPin_QueryInterface(IPin *iface, REFIID riid, void **ppv) { GSTOutPin *This = (GSTOutPin *)iface; TRACE("(%s, %p)\n", debugstr_guid(riid), ppv); *ppv = NULL; if (IsEqualIID(riid, &IID_IUnknown)) *ppv = iface; else if (IsEqualIID(riid, &IID_IPin)) *ppv = iface; else if (IsEqualIID(riid, &IID_IMediaSeeking)) *ppv = &This->seek; else if (IsEqualIID(riid, &IID_IQualityControl)) *ppv = &This->IQualityControl_iface; if (*ppv) { IUnknown_AddRef((IUnknown *)(*ppv)); return S_OK; } FIXME("No interface for %s!\n", debugstr_guid(riid)); return E_NOINTERFACE; } static ULONG WINAPI GSTOutPin_Release(IPin *iface) { GSTOutPin *This = (GSTOutPin *)iface; ULONG refCount = InterlockedDecrement(&This->pin.pin.refCount); TRACE("(%p)->() Release from %d\n", iface, refCount + 1); if (!refCount) { if (This->their_src) gst_pad_unlink(This->their_src, This->my_sink); gst_object_unref(This->my_sink); CloseHandle(This->caps_event); DeleteMediaType(This->pmt); FreeMediaType(&This->pin.pin.mtCurrent); gst_segment_free(This->segment); if (This->pin.pAllocator) IMemAllocator_Release(This->pin.pAllocator); CoTaskMemFree(This); return 0; } return refCount; } static HRESULT WINAPI GSTOutPin_GetMediaType(BasePin *iface, int iPosition, AM_MEDIA_TYPE *pmt) { GSTOutPin *This = (GSTOutPin *)iface; if (iPosition < 0) return E_INVALIDARG; if (iPosition > 0) return VFW_S_NO_MORE_ITEMS; CopyMediaType(pmt, This->pmt); return S_OK; } static HRESULT WINAPI GSTOutPin_DecideBufferSize(BaseOutputPin *iface, IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *ppropInputRequest) { /* Unused */ return S_OK; } static HRESULT WINAPI GSTOutPin_DecideAllocator(BaseOutputPin *iface, IMemInputPin *pPin, IMemAllocator **pAlloc) { HRESULT hr; GSTOutPin *This = (GSTOutPin *)iface; GSTImpl *GSTfilter = (GSTImpl*)This->pin.pin.pinInfo.pFilter; *pAlloc = NULL; if (GSTfilter->pInputPin.pAlloc) { hr = IMemInputPin_NotifyAllocator(pPin, GSTfilter->pInputPin.pAlloc, FALSE); if (SUCCEEDED(hr)) { *pAlloc = GSTfilter->pInputPin.pAlloc; IMemAllocator_AddRef(*pAlloc); } } else hr = VFW_E_NO_ALLOCATOR; return hr; } static HRESULT WINAPI GSTOutPin_BreakConnect(BaseOutputPin *This) { HRESULT hr; TRACE("(%p)->()\n", This); EnterCriticalSection(This->pin.pCritSec); if (!This->pin.pConnectedTo || !This->pMemInputPin) hr = VFW_E_NOT_CONNECTED; else { hr = IPin_Disconnect(This->pin.pConnectedTo); IPin_Disconnect((IPin *)This); } LeaveCriticalSection(This->pin.pCritSec); return hr; } static const IPinVtbl GST_OutputPin_Vtbl = { GSTOutPin_QueryInterface, BasePinImpl_AddRef, GSTOutPin_Release, BaseOutputPinImpl_Connect, BaseOutputPinImpl_ReceiveConnection, BaseOutputPinImpl_Disconnect, BasePinImpl_ConnectedTo, BasePinImpl_ConnectionMediaType, BasePinImpl_QueryPinInfo, BasePinImpl_QueryDirection, BasePinImpl_QueryId, GST_OutPin_QueryAccept, BasePinImpl_EnumMediaTypes, BasePinImpl_QueryInternalConnections, BaseOutputPinImpl_EndOfStream, BaseOutputPinImpl_BeginFlush, BaseOutputPinImpl_EndFlush, BasePinImpl_NewSegment }; static const BasePinFuncTable output_BaseFuncTable = { NULL, BaseOutputPinImpl_AttemptConnection, BasePinImpl_GetMediaTypeVersion, GSTOutPin_GetMediaType }; static const BaseOutputPinFuncTable output_BaseOutputFuncTable = { GSTOutPin_DecideBufferSize, GSTOutPin_DecideAllocator, GSTOutPin_BreakConnect }; static HRESULT GST_AddPin(GSTImpl *This, const PIN_INFO *piOutput, const AM_MEDIA_TYPE *amt) { HRESULT hr; This->ppPins = CoTaskMemRealloc(This->ppPins, (This->cStreams + 1) * sizeof(IPin *)); hr = BaseOutputPin_Construct(&GST_OutputPin_Vtbl, sizeof(GSTOutPin), piOutput, &output_BaseFuncTable, &output_BaseOutputFuncTable, &This->filter.csFilter, (IPin**)(This->ppPins + This->cStreams)); if (SUCCEEDED(hr)) { GSTOutPin *pin = This->ppPins[This->cStreams]; pin->pmt = CoTaskMemAlloc(sizeof(AM_MEDIA_TYPE)); CopyMediaType(pin->pmt, amt); pin->pin.pin.pinInfo.pFilter = (LPVOID)This; pin->caps_event = CreateEventW(NULL, 0, 0, NULL); pin->segment = gst_segment_new(); This->cStreams++; pin->IQualityControl_iface.lpVtbl = &GSTOutPin_QualityControl_Vtbl; SourceSeeking_Init(&pin->seek, &GST_Seeking_Vtbl, GST_ChangeStop, GST_ChangeCurrent, GST_ChangeRate, &This->filter.csFilter); BaseFilterImpl_IncrementPinVersion((BaseFilter*)This); } else ERR("Failed with error %x\n", hr); return hr; } static HRESULT GST_RemoveOutputPins(GSTImpl *This) { HRESULT hr; ULONG i; GSTOutPin **ppOldPins = This->ppPins; TRACE("(%p)\n", This); if (!This->gstfilter) return S_OK; gst_element_set_bus(This->gstfilter, NULL); gst_element_set_state(This->gstfilter, GST_STATE_NULL); gst_pad_unlink(This->my_src, This->their_sink); if (This->push_thread) gst_pad_activate_push(This->my_src, 0); gst_object_unref(This->my_src); This->my_src = This->their_sink = NULL; for (i = 0; i < This->cStreams; i++) { hr = BaseOutputPinImpl_BreakConnect(&ppOldPins[i]->pin); TRACE("Disconnect: %08x\n", hr); IPin_Release((IPin*)ppOldPins[i]); } This->cStreams = 0; This->ppPins = NULL; gst_object_unref(This->gstfilter); This->gstfilter = NULL; BaseFilterImpl_IncrementPinVersion((BaseFilter*)This); CoTaskMemFree(ppOldPins); return S_OK; } static ULONG WINAPI GSTInPin_Release(IPin *iface) { GSTInPin *This = (GSTInPin*)iface; ULONG refCount = InterlockedDecrement(&This->pin.refCount); TRACE("(%p)->() Release from %d\n", iface, refCount + 1); if (!refCount) { FreeMediaType(&This->pin.mtCurrent); if (This->pAlloc) IMemAllocator_Release(This->pAlloc); This->pAlloc = NULL; This->pin.IPin_iface.lpVtbl = NULL; return 0; } else return refCount; } static HRESULT WINAPI GSTInPin_ReceiveConnection(IPin *iface, IPin *pReceivePin, const AM_MEDIA_TYPE *pmt) { PIN_DIRECTION pindirReceive; HRESULT hr = S_OK; GSTInPin *This = (GSTInPin*)iface; TRACE("(%p/%p)->(%p, %p)\n", This, iface, pReceivePin, pmt); dump_AM_MEDIA_TYPE(pmt); EnterCriticalSection(This->pin.pCritSec); if (!This->pin.pConnectedTo) { ALLOCATOR_PROPERTIES props; props.cBuffers = 8; props.cbBuffer = 16384; props.cbAlign = 1; props.cbPrefix = 0; if (SUCCEEDED(hr) && IPin_QueryAccept(iface, pmt) != S_OK) hr = VFW_E_TYPE_NOT_ACCEPTED; if (SUCCEEDED(hr)) { IPin_QueryDirection(pReceivePin, &pindirReceive); if (pindirReceive != PINDIR_OUTPUT) { ERR("Can't connect from non-output pin\n"); hr = VFW_E_INVALID_DIRECTION; } } This->pReader = NULL; This->pAlloc = NULL; if (SUCCEEDED(hr)) hr = IPin_QueryInterface(pReceivePin, &IID_IAsyncReader, (LPVOID *)&This->pReader); if (SUCCEEDED(hr)) hr = GST_Connect(This, pReceivePin, &props); if (SUCCEEDED(hr)) hr = IAsyncReader_RequestAllocator(This->pReader, NULL, &props, &This->pAlloc); if (SUCCEEDED(hr)) { CopyMediaType(&This->pin.mtCurrent, pmt); This->pin.pConnectedTo = pReceivePin; IPin_AddRef(pReceivePin); hr = IMemAllocator_Commit(This->pAlloc); } else { GST_RemoveOutputPins((GSTImpl *)This->pin.pinInfo.pFilter); if (This->pReader) IAsyncReader_Release(This->pReader); This->pReader = NULL; if (This->pAlloc) IMemAllocator_Release(This->pAlloc); This->pAlloc = NULL; } TRACE("Size: %i\n", props.cbBuffer); } else hr = VFW_E_ALREADY_CONNECTED; LeaveCriticalSection(This->pin.pCritSec); return hr; } static HRESULT WINAPI GSTInPin_Disconnect(IPin *iface) { HRESULT hr; GSTInPin *This = (GSTInPin*)iface; FILTER_STATE state; TRACE("()\n"); hr = IBaseFilter_GetState(This->pin.pinInfo.pFilter, INFINITE, &state); EnterCriticalSection(This->pin.pCritSec); if (This->pin.pConnectedTo) { GSTImpl *Parser = (GSTImpl *)This->pin.pinInfo.pFilter; if (SUCCEEDED(hr) && state == State_Stopped) { IMemAllocator_Decommit(This->pAlloc); IPin_Disconnect(This->pin.pConnectedTo); This->pin.pConnectedTo = NULL; hr = GST_RemoveOutputPins(Parser); } else hr = VFW_E_NOT_STOPPED; } else hr = S_FALSE; LeaveCriticalSection(This->pin.pCritSec); return hr; } static HRESULT WINAPI GSTInPin_QueryAccept(IPin *iface, const AM_MEDIA_TYPE *pmt) { GSTInPin *This = (GSTInPin*)iface; TRACE("(%p)->(%p)\n", This, pmt); dump_AM_MEDIA_TYPE(pmt); if (IsEqualIID(&pmt->majortype, &MEDIATYPE_Stream)) return S_OK; return S_FALSE; } static HRESULT WINAPI GSTInPin_EndOfStream(IPin *iface) { GSTInPin *pin = (GSTInPin*)iface; GSTImpl *This = (GSTImpl*)pin->pin.pinInfo.pFilter; FIXME("Propagate message on %p\n", This); return S_OK; } static HRESULT WINAPI GSTInPin_BeginFlush(IPin *iface) { GSTInPin *pin = (GSTInPin*)iface; GSTImpl *This = (GSTImpl*)pin->pin.pinInfo.pFilter; FIXME("Propagate message on %p\n", This); return S_OK; } static HRESULT WINAPI GSTInPin_EndFlush(IPin *iface) { GSTInPin *pin = (GSTInPin*)iface; GSTImpl *This = (GSTImpl*)pin->pin.pinInfo.pFilter; FIXME("Propagate message on %p\n", This); return S_OK; } static HRESULT WINAPI GSTInPin_NewSegment(IPin *iface, REFERENCE_TIME tStart, REFERENCE_TIME tStop, double dRate) { GSTInPin *pin = (GSTInPin*)iface; GSTImpl *This = (GSTImpl*)pin->pin.pinInfo.pFilter; BasePinImpl_NewSegment(iface, tStart, tStop, dRate); FIXME("Propagate message on %p\n", This); return S_OK; } static HRESULT WINAPI GSTInPin_QueryInterface(IPin * iface, REFIID riid, LPVOID * ppv) { GSTInPin *This = (GSTInPin*)iface; TRACE("(%p/%p)->(%s, %p)\n", This, iface, debugstr_guid(riid), ppv); *ppv = NULL; if (IsEqualIID(riid, &IID_IUnknown)) *ppv = iface; else if (IsEqualIID(riid, &IID_IPin)) *ppv = iface; else if (IsEqualIID(riid, &IID_IMediaSeeking)) { return IBaseFilter_QueryInterface(This->pin.pinInfo.pFilter, &IID_IMediaSeeking, ppv); } if (*ppv) { IUnknown_AddRef((IUnknown *)(*ppv)); return S_OK; } FIXME("No interface for %s!\n", debugstr_guid(riid)); return E_NOINTERFACE; } static HRESULT WINAPI GSTInPin_EnumMediaTypes(IPin *iface, IEnumMediaTypes **ppEnum) { BasePin *This = (BasePin *)iface; TRACE("(%p/%p)->(%p)\n", This, iface, ppEnum); return EnumMediaTypes_Construct(This, BasePinImpl_GetMediaType, BasePinImpl_GetMediaTypeVersion, ppEnum); } static const IPinVtbl GST_InputPin_Vtbl = { GSTInPin_QueryInterface, BasePinImpl_AddRef, GSTInPin_Release, BaseInputPinImpl_Connect, GSTInPin_ReceiveConnection, GSTInPin_Disconnect, BasePinImpl_ConnectedTo, BasePinImpl_ConnectionMediaType, BasePinImpl_QueryPinInfo, BasePinImpl_QueryDirection, BasePinImpl_QueryId, GSTInPin_QueryAccept, GSTInPin_EnumMediaTypes, BasePinImpl_QueryInternalConnections, GSTInPin_EndOfStream, GSTInPin_BeginFlush, GSTInPin_EndFlush, GSTInPin_NewSegment };