From d1d7fb884d8e6fb9a93f44591aa8a69f332c95f6 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Mon, 2 Jun 2008 18:23:54 +0000 Subject: Add a new win32 videosink. Uses the DirectShow renderers for high-performance video rendering on win32. Original commit message from CVS: * configure.ac: * sys/Makefile.am: * sys/dshowvideosink/Makefile.am: * sys/dshowvideosink/README: * sys/dshowvideosink/dshowvideofakesrc.cpp: * sys/dshowvideosink/dshowvideofakesrc.h: * sys/dshowvideosink/dshowvideosink.cpp: * sys/dshowvideosink/dshowvideosink.h: Add a new win32 videosink. Uses the DirectShow renderers for high-performance video rendering on win32. Currently only supports some YUV formats. Rank PRIMARY, since it's much more useful for the common cases that the directdraw sink (which only does RGB). --- configure.ac | 2 + sys/Makefile.am | 3 +- sys/dshowvideosink/Makefile.am | 9 + sys/dshowvideosink/README | 5 + sys/dshowvideosink/dshowvideofakesrc.cpp | 281 ++++++ sys/dshowvideosink/dshowvideofakesrc.h | 70 ++ sys/dshowvideosink/dshowvideosink.cpp | 1542 ++++++++++++++++++++++++++++++ sys/dshowvideosink/dshowvideosink.h | 104 ++ 8 files changed, 2015 insertions(+), 1 deletion(-) create mode 100644 sys/dshowvideosink/Makefile.am create mode 100644 sys/dshowvideosink/README create mode 100644 sys/dshowvideosink/dshowvideofakesrc.cpp create mode 100644 sys/dshowvideosink/dshowvideofakesrc.h create mode 100644 sys/dshowvideosink/dshowvideosink.cpp create mode 100644 sys/dshowvideosink/dshowvideosink.h diff --git a/configure.ac b/configure.ac index 5efa9357..a8a66bb7 100644 --- a/configure.ac +++ b/configure.ac @@ -1188,6 +1188,7 @@ gst-libs/gst/dshow/Makefile sys/Makefile sys/dshowdecwrapper/Makefile sys/dshowsrcwrapper/Makefile +sys/dshowvideosink/Makefile sys/dvb/Makefile sys/fbdev/Makefile sys/oss4/Makefile @@ -1219,6 +1220,7 @@ ext/Makefile ext/nas/Makefile ext/mpeg2enc/Makefile ext/mplex/Makefile +ext/mozilla/Makefile ext/musepack/Makefile ext/musicbrainz/Makefile ext/mythtv/Makefile diff --git a/sys/Makefile.am b/sys/Makefile.am index aa77cd6e..5a7e2a04 100644 --- a/sys/Makefile.am +++ b/sys/Makefile.am @@ -54,5 +54,6 @@ endif SUBDIRS = $(DVB_DIR) $(FBDEV_DIR) $(OSS4_DIR) $(QT_DIR) $(VCD_DIR) $(WININET_DIR) -DIST_SUBDIRS = dvb fbdev dshowdecwrapper dshowsrcwrapper oss4 qtwrapper vcd wininet +DIST_SUBDIRS = dvb fbdev dshowdecwrapper dshowsrcwrapper dshowvideosink \ + oss4 qtwrapper vcd wininet diff --git a/sys/dshowvideosink/Makefile.am b/sys/dshowvideosink/Makefile.am new file mode 100644 index 00000000..1fa81b75 --- /dev/null +++ b/sys/dshowvideosink/Makefile.am @@ -0,0 +1,9 @@ +# This plugin isn't buildable with autotools at this point in time, so just +# ensure everything's listed in EXTRA_DIST + +EXTRA_DIST = \ + dshowvideofakesrc.cpp \ + dshowvideofakesrc.h \ + dshowvideosink.cpp \ + dshowvideosink.h + diff --git a/sys/dshowvideosink/README b/sys/dshowvideosink/README new file mode 100644 index 00000000..af296dc9 --- /dev/null +++ b/sys/dshowvideosink/README @@ -0,0 +1,5 @@ +To build this, you'll require the DirectShow base classes. These are supplied +in the Windows SDK, but under Samples\Multimedia\DirectShow\BaseClasses + +Once you've built that, you should be able to figure out the rest... + diff --git a/sys/dshowvideosink/dshowvideofakesrc.cpp b/sys/dshowvideosink/dshowvideofakesrc.cpp new file mode 100644 index 00000000..40194123 --- /dev/null +++ b/sys/dshowvideosink/dshowvideofakesrc.cpp @@ -0,0 +1,281 @@ +/* GStreamer + * Copyright (C) 2008 Michael Smith + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "dshowvideofakesrc.h" + +// {A0A5CF33-BD0C-4158-9A56-3011DEE3AF6B} +const GUID CLSID_VideoFakeSrc = +{ 0xa0a5cf33, 0xbd0c, 0x4158, { 0x9a, 0x56, 0x30, 0x11, 0xde, 0xe3, 0xaf, 0x6b } }; + +/* output pin*/ +VideoFakeSrcPin::VideoFakeSrcPin (CBaseFilter *pFilter, CCritSec *sec, HRESULT *hres): + CBaseOutputPin("VideoFakeSrcPin", pFilter, sec, hres, L"output") +{ +} + +VideoFakeSrcPin::~VideoFakeSrcPin() +{ +} + +HRESULT VideoFakeSrcPin::GetMediaType(int iPosition, CMediaType *pMediaType) +{ + GST_DEBUG ("GetMediaType(%d) called", iPosition); + if(iPosition == 0) { + *pMediaType = m_MediaType; + return S_OK; + } + + return VFW_S_NO_MORE_ITEMS; +} + +/* This seems to be called to notify us of the actual media type being used, + * even though SetMediaType isn't called. How bizarre! */ +HRESULT VideoFakeSrcPin::CheckMediaType(const CMediaType *pmt) +{ + GST_DEBUG ("CheckMediaType called: %p", pmt); + + /* The video renderer will request a different stride, which we must accept. + * So, we accept arbitrary strides (and do memcpy() to convert if needed), + * and require the rest of the media type to match + */ + if (IsEqualGUID(pmt->majortype,m_MediaType.majortype) && + IsEqualGUID(pmt->subtype,m_MediaType.subtype) && + IsEqualGUID(pmt->formattype,m_MediaType.formattype) && + pmt->cbFormat >= m_MediaType.cbFormat) + { + if (IsEqualGUID(pmt->formattype, FORMAT_VideoInfo)) { + VIDEOINFOHEADER *newvh = (VIDEOINFOHEADER *)pmt->pbFormat; + VIDEOINFOHEADER *curvh = (VIDEOINFOHEADER *)m_MediaType.pbFormat; + + if ((memcmp ((void *)&newvh->rcSource, (void *)&curvh->rcSource, sizeof (RECT)) == 0) && + (memcmp ((void *)&newvh->rcTarget, (void *)&curvh->rcTarget, sizeof (RECT)) == 0) && + (newvh->bmiHeader.biCompression == curvh->bmiHeader.biCompression) && + (newvh->bmiHeader.biHeight == curvh->bmiHeader.biHeight) && + (newvh->bmiHeader.biWidth >= curvh->bmiHeader.biWidth)) + { + GST_DEBUG ("CheckMediaType has same media type, width %d (%d image)", newvh->bmiHeader.biWidth, curvh->bmiHeader.biWidth); + + /* OK, compatible! */ + return S_OK; + } + else { + GST_WARNING ("Looked similar, but aren't..."); + } + } + + } + GST_WARNING ("Different media types, FAILING!"); + return S_FALSE; +} + +HRESULT VideoFakeSrcPin::DecideBufferSize (IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *ppropInputRequest) +{ + ALLOCATOR_PROPERTIES properties; + GST_DEBUG ("Required allocator properties: %d, %d, %d, %d", + ppropInputRequest->cbAlign, ppropInputRequest->cbBuffer, + ppropInputRequest->cbPrefix, ppropInputRequest->cBuffers); + + ppropInputRequest->cbBuffer = m_SampleSize; + ppropInputRequest->cBuffers = 1; + + /* First set the buffer descriptions we're interested in */ + HRESULT hres = pAlloc->SetProperties(ppropInputRequest, &properties); + GST_DEBUG ("Actual Allocator properties: %d, %d, %d, %d", + properties.cbAlign, properties.cbBuffer, + properties.cbPrefix, properties.cBuffers); + + /* Then actually allocate the buffers */ + pAlloc->Commit(); + + return S_OK; +} + +STDMETHODIMP +VideoFakeSrcPin::Notify(IBaseFilter * pSender, Quality q) +{ + /* Implementing this usefully is not required, but the base class + * has an assertion here... */ + /* TODO: Map this to GStreamer QOS events? */ + return E_NOTIMPL; +} + +STDMETHODIMP VideoFakeSrcPin::SetMediaType (AM_MEDIA_TYPE *pmt) +{ + m_MediaType.Set (*pmt); + m_SampleSize = m_MediaType.GetSampleSize(); + + GST_DEBUG ("SetMediaType called. SampleSize is %d", m_SampleSize); + + return S_OK; +} + +/* If the destination buffer is a different shape (strides, etc.) from the source + * buffer, we have to copy. Do that here, for supported video formats. + * + * TODO: When possible (when these things DON'T differ), we should buffer-alloc the + * final output buffer, and not do this copy */ +STDMETHODIMP VideoFakeSrcPin::CopyToDestinationBuffer (byte *srcbuf, byte *dstbuf) +{ + VIDEOINFOHEADER *vh = (VIDEOINFOHEADER *)m_MediaType.pbFormat; + GST_DEBUG ("Rendering a frame"); + + byte *src, *dst; + int dststride, srcstride, rows; + guint32 fourcc = vh->bmiHeader.biCompression; + + /* biHeight is always negative; we don't want that. */ + int height = ABS (vh->bmiHeader.biHeight); + int width = vh->bmiHeader.biWidth; + + /* YUY2 is the preferred layout for DirectShow, so we will probably get this + * most of the time */ + if ((fourcc == GST_MAKE_FOURCC ('Y', 'U', 'Y', '2')) || + (fourcc == GST_MAKE_FOURCC ('Y', 'U', 'Y', 'V')) || + (fourcc == GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'))) + { + /* Nice and simple */ + int srcstride = GST_ROUND_UP_4 (vh->rcSource.right * 2); + int dststride = width * 2; + + for (int i = 0; i < height; i++) { + memcpy (dstbuf + dststride * i, srcbuf + srcstride * i, srcstride); + } + } + else if (fourcc == GST_MAKE_FOURCC ('Y', 'V', '1', '2')) { + for (int component = 0; component < 3; component++) { + // TODO: Get format properly rather than hard-coding it. Use gst_video_* APIs *? + if (component == 0) { + srcstride = GST_ROUND_UP_4 (vh->rcSource.right); + src = srcbuf; + } + else { + srcstride = GST_ROUND_UP_4 ( GST_ROUND_UP_2 (vh->rcSource.right) / 2); + if (component == 1) + src = srcbuf + GST_ROUND_UP_4 (vh->rcSource.right) * GST_ROUND_UP_2 (vh->rcSource.bottom); + else + src = srcbuf + GST_ROUND_UP_4 (vh->rcSource.right) * GST_ROUND_UP_2 (vh->rcSource.bottom) + + srcstride * (GST_ROUND_UP_2 (vh->rcSource.bottom) / 2); + } + + /* Is there a better way to do this? This is ICK! */ + if (component == 0) { + dststride = width; + dst = dstbuf; + rows = height; + } else if (component == 1) { + dststride = width / 2; + dst = dstbuf + width * height; + rows = height/2; + } + else { + dststride = width / 2; + dst = dstbuf + width * height + + width/2 * height/2; + rows = height/2; + } + + for (int i = 0; i < rows; i++) { + memcpy (dst + i * dststride, src + i * srcstride, srcstride); + } + } + } + + return S_OK; +} + + +GstFlowReturn VideoFakeSrcPin::PushBuffer(GstBuffer *buffer) +{ + IMediaSample *pSample = NULL; + + byte *data = GST_BUFFER_DATA (buffer); + + /* TODO: Use more of the arguments here? */ + HRESULT hres = GetDeliveryBuffer(&pSample, NULL, NULL, 0); + if (SUCCEEDED (hres)) + { + BYTE *sample_buffer; + AM_MEDIA_TYPE *mediatype; + + pSample->GetPointer(&sample_buffer); + pSample->GetMediaType(&mediatype); + if (mediatype) + SetMediaType (mediatype); + + if(sample_buffer) + { + /* Copy to the destination stride. + * This is not just a simple memcpy because of the different strides. + * TODO: optimise for the same-stride case and avoid the copy entirely. + */ + CopyToDestinationBuffer (data, sample_buffer); + } + + pSample->SetDiscontinuity(FALSE); /* Decoded frame; unimportant */ + pSample->SetSyncPoint(TRUE); /* Decoded frame; always a valid syncpoint */ + pSample->SetPreroll(FALSE); /* For non-displayed frames. + Not used in GStreamer */ + + /* Disable synchronising on this sample. We instead let GStreamer handle + * this at a higher level, inside BaseSink. */ + pSample->SetTime(NULL, NULL); + + hres = Deliver(pSample); + pSample->Release(); + + if (SUCCEEDED (hres)) + return GST_FLOW_OK; + else if (hres == VFW_E_NOT_CONNECTED) + return GST_FLOW_NOT_LINKED; + else + return GST_FLOW_ERROR; + } + else { + GST_WARNING ("Could not get sample for delivery to sink: %x", hres); + return GST_FLOW_ERROR; + } +} + +STDMETHODIMP VideoFakeSrcPin::Flush () +{ + DeliverBeginFlush(); + DeliverEndFlush(); + return S_OK; +} + +VideoFakeSrc::VideoFakeSrc() : CBaseFilter("VideoFakeSrc", NULL, &m_critsec, CLSID_VideoFakeSrc) +{ + HRESULT hr = S_OK; + m_pOutputPin = new VideoFakeSrcPin ((CSource *)this, &m_critsec, &hr); +} + +int VideoFakeSrc::GetPinCount() +{ + return 1; +} + +CBasePin *VideoFakeSrc::GetPin(int n) +{ + return (CBasePin *)m_pOutputPin; +} + +VideoFakeSrcPin *VideoFakeSrc::GetOutputPin() +{ + return m_pOutputPin; +} diff --git a/sys/dshowvideosink/dshowvideofakesrc.h b/sys/dshowvideosink/dshowvideofakesrc.h new file mode 100644 index 00000000..afbb1bfe --- /dev/null +++ b/sys/dshowvideosink/dshowvideofakesrc.h @@ -0,0 +1,70 @@ +/* GStreamer + * Copyright (C) 2008 Michael Smith + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifndef __DSHOWVIDEOFAKESRC_H__ +#define __DSHOWVIDEOFAKESRC_H__ + +#include +#include + +class VideoFakeSrcPin : public CBaseOutputPin +{ +protected: + /* members */ + CMediaType m_MediaType; + unsigned int m_SampleSize; + +public: + /* methods */ + VideoFakeSrcPin (CBaseFilter *pFilter, CCritSec *sec, HRESULT *hres); + ~VideoFakeSrcPin (); + + STDMETHODIMP CopyToDestinationBuffer (byte *src, byte *dst); + GstFlowReturn (PushBuffer) (GstBuffer *buf); + + /* Overrides */ + virtual HRESULT CheckMediaType(const CMediaType *pmt); + HRESULT GetMediaType(int iPosition, CMediaType *pMediaType); + virtual HRESULT DecideBufferSize (IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *ppropInputRequest); + STDMETHOD (SetMediaType) (AM_MEDIA_TYPE *pmt); + STDMETHOD (Flush) (); + STDMETHODIMP Notify(IBaseFilter * pSender, Quality q); + + +}; + +class VideoFakeSrc : public CBaseFilter +{ +private: + /* members */ + CCritSec m_critsec; + VideoFakeSrcPin *m_pOutputPin; + +public: + /* methods */ + VideoFakeSrc (void); + ~VideoFakeSrc (void) {}; + + VideoFakeSrcPin *GetOutputPin(); + + /* Overrides */ + int GetPinCount(); + CBasePin *GetPin(int n); +}; + +#endif /* __DSHOWVIDEOFAKESRC_H__ */ \ No newline at end of file diff --git a/sys/dshowvideosink/dshowvideosink.cpp b/sys/dshowvideosink/dshowvideosink.cpp new file mode 100644 index 00000000..fb8ea16d --- /dev/null +++ b/sys/dshowvideosink/dshowvideosink.cpp @@ -0,0 +1,1542 @@ +/* GStreamer + * Copyright (C) 2008 Michael Smith + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "dshowvideosink.h" +#include "dshowvideofakesrc.h" + +#include + +#include "windows.h" + +static const GstElementDetails gst_dshowvideosink_details = +GST_ELEMENT_DETAILS ("DirectShow video sink", + "Sink/Video", + "Display data using a DirectShow video renderer", + "Michael Smith "); + +GST_DEBUG_CATEGORY_STATIC (dshowvideosink_debug); +#define GST_CAT_DEFAULT dshowvideosink_debug + +static GstCaps * gst_directshow_media_type_to_caps (AM_MEDIA_TYPE *mediatype); +static gboolean gst_caps_to_directshow_media_type (GstCaps *caps, AM_MEDIA_TYPE *mediatype); + +/* TODO: Support RGB! */ +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ( + "video/x-raw-yuv," + "width = (int) [ 1, MAX ]," + "height = (int) [ 1, MAX ]," + "framerate = (fraction) [ 0, MAX ]," + "format = {(fourcc)YUY2, (fourcc)UYVY, (fourcc) YUVY, (fourcc)YV12 }") + ); + +static void gst_dshowvideosink_init_interfaces (GType type); + +GST_BOILERPLATE_FULL (GstDshowVideoSink, gst_dshowvideosink, GstBaseSink, + GST_TYPE_BASE_SINK, gst_dshowvideosink_init_interfaces); + +enum +{ + PROP_0, + PROP_KEEP_ASPECT_RATIO, + PROP_FULL_SCREEN, + PROP_RENDERER +}; + +/* GObject methods */ +static void gst_dshowvideosink_finalize (GObject * gobject); +static void gst_dshowvideosink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_dshowvideosink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +/* GstElement methods */ +static GstStateChangeReturn gst_dshowvideosink_change_state (GstElement * element, GstStateChange transition); + +/* GstBaseSink methods */ +static gboolean gst_dshowvideosink_start (GstBaseSink * bsink); +static gboolean gst_dshowvideosink_stop (GstBaseSink * bsink); +static gboolean gst_dshowvideosink_unlock (GstBaseSink * bsink); +static gboolean gst_dshowvideosink_unlock_stop (GstBaseSink * bsink); +static gboolean gst_dshowvideosink_set_caps (GstBaseSink * bsink, GstCaps * caps); +static GstCaps *gst_dshowvideosink_get_caps (GstBaseSink * bsink); +static GstFlowReturn gst_dshowvideosink_render (GstBaseSink *sink, GstBuffer *buffer); + +/* GstXOverlay methods */ +static void gst_dshowvideosink_set_window_id (GstXOverlay * overlay, ULONG window_id); + +/* TODO: event, preroll, buffer_alloc? + * buffer_alloc won't generally be all that useful because the renderers require a + * different stride to GStreamer's implicit values. + */ + +static gboolean +gst_dshowvideosink_interface_supported (GstImplementsInterface * iface, + GType type) +{ + g_assert (type == GST_TYPE_X_OVERLAY); + return TRUE; +} + +static void +gst_dshowvideosink_interface_init (GstImplementsInterfaceClass * klass) +{ + klass->supported = gst_dshowvideosink_interface_supported; +} + + +static void +gst_dshowvideosink_xoverlay_interface_init (GstXOverlayClass * iface) +{ + iface->set_xwindow_id = gst_dshowvideosink_set_window_id; +} + +static void +gst_dshowvideosink_init_interfaces (GType type) +{ + static const GInterfaceInfo iface_info = { + (GInterfaceInitFunc) gst_dshowvideosink_interface_init, + NULL, + NULL, + }; + + static const GInterfaceInfo xoverlay_info = { + (GInterfaceInitFunc) gst_dshowvideosink_xoverlay_interface_init, + NULL, + NULL, + }; + + g_type_add_interface_static (type, GST_TYPE_IMPLEMENTS_INTERFACE, + &iface_info); + g_type_add_interface_static (type, GST_TYPE_X_OVERLAY, &xoverlay_info); + + GST_DEBUG_CATEGORY_INIT (dshowvideosink_debug, "dshowvideosink", 0, \ + "DirectShow video sink"); +} +static void +gst_dshowvideosink_base_init (gpointer klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_template)); + + gst_element_class_set_details (element_class, &gst_dshowvideosink_details); +} + +static void +gst_dshowvideosink_class_init (GstDshowVideoSinkClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + GstBaseSinkClass *gstbasesink_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + gstbasesink_class = (GstBaseSinkClass *) klass; + + gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_dshowvideosink_finalize); + gobject_class->set_property = + GST_DEBUG_FUNCPTR (gst_dshowvideosink_set_property); + gobject_class->get_property = + GST_DEBUG_FUNCPTR (gst_dshowvideosink_get_property); + + gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_dshowvideosink_change_state); + + gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_dshowvideosink_get_caps); + gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_dshowvideosink_set_caps); + gstbasesink_class->start = GST_DEBUG_FUNCPTR (gst_dshowvideosink_start); + gstbasesink_class->stop = GST_DEBUG_FUNCPTR (gst_dshowvideosink_stop); + gstbasesink_class->unlock = GST_DEBUG_FUNCPTR (gst_dshowvideosink_unlock); + gstbasesink_class->unlock_stop = + GST_DEBUG_FUNCPTR (gst_dshowvideosink_unlock_stop); + gstbasesink_class->render = GST_DEBUG_FUNCPTR (gst_dshowvideosink_render); + + /* Add properties */ + g_object_class_install_property (G_OBJECT_CLASS (klass), + PROP_KEEP_ASPECT_RATIO, g_param_spec_boolean ("force-aspect-ratio", + "Force aspect ratio", + "When enabled, scaling will respect original aspect ratio", FALSE, + (GParamFlags)G_PARAM_READWRITE)); + g_object_class_install_property (G_OBJECT_CLASS (klass), + PROP_FULL_SCREEN, g_param_spec_boolean ("fullscreen", + "Full screen mode", + "Use full-screen mode (not available when using XOverlay)", FALSE, + (GParamFlags)G_PARAM_READWRITE)); + + g_object_class_install_property (G_OBJECT_CLASS (klass), + PROP_RENDERER, g_param_spec_string ("renderer", "Renderer", + "Force usage of specific DirectShow renderer (VMR9 or VMR)", + NULL, (GParamFlags)G_PARAM_READWRITE)); +} + +static void +gst_dshowvideosink_clear (GstDshowVideoSink *sink) +{ + sink->renderersupport = NULL; + sink->fakesrc = NULL; + sink->filter_graph = NULL; + + sink->keep_aspect_ratio = FALSE; + sink->full_screen = FALSE; + + sink->window_closed = FALSE; + sink->window_id = NULL; + + sink->connected = FALSE; +} + +static void +gst_dshowvideosink_init (GstDshowVideoSink * sink, GstDshowVideoSinkClass * klass) +{ + gst_dshowvideosink_clear (sink); + + CoInitializeEx (NULL, COINIT_MULTITHREADED); + + /* TODO: Copied from GstVideoSink; should we use that as base class? */ + /* 20ms is more than enough, 80-130ms is noticable */ + gst_base_sink_set_max_lateness (GST_BASE_SINK (sink), 20 * GST_MSECOND); + gst_base_sink_set_qos_enabled (GST_BASE_SINK (sink), TRUE); +} + +static void +gst_dshowvideosink_finalize (GObject * gobject) +{ + GstDshowVideoSink *sink = GST_DSHOWVIDEOSINK (gobject); + + if (sink->preferredrenderer) + g_free (sink->preferredrenderer); + + CoUninitialize (); + + G_OBJECT_CLASS (parent_class)->finalize (gobject); +} + +static void +gst_dshowvideosink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstDshowVideoSink *sink = GST_DSHOWVIDEOSINK (object); + + switch (prop_id) { + case PROP_RENDERER: + if (sink->preferredrenderer) + g_free (sink->preferredrenderer); + + sink->preferredrenderer = g_value_dup_string (value); + break; + case PROP_KEEP_ASPECT_RATIO: + sink->keep_aspect_ratio = g_value_get_boolean (value); + break; + case PROP_FULL_SCREEN: + sink->full_screen = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_dshowvideosink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstDshowVideoSink *sink = GST_DSHOWVIDEOSINK (object); + + switch (prop_id) { + case PROP_RENDERER: + g_value_take_string (value, sink->preferredrenderer); + break; + case PROP_KEEP_ASPECT_RATIO: + g_value_set_boolean (value, sink->keep_aspect_ratio); + break; + case PROP_FULL_SCREEN: + g_value_set_boolean (value, sink->full_screen); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GstCaps * +gst_dshowvideosink_get_caps (GstBaseSink * basesink) +{ + GstDshowVideoSink *sink = GST_DSHOWVIDEOSINK (basesink); + + return NULL; +} + +static void dump_available_media_types (IPin *pin) +{ + /* Enumerate all media types on this pin, output info about them */ + IEnumMediaTypes *enumerator = NULL; + AM_MEDIA_TYPE *type; + GstCaps *caps; + int i = 0; + + GST_INFO ("Enumerating media types on pin %p", pin); + + pin->EnumMediaTypes (&enumerator); + + while (enumerator->Next (1, &type, NULL) == S_OK) { + i++; + caps = gst_directshow_media_type_to_caps (type); + + if (caps) { + gchar *str = gst_caps_to_string (caps); + GST_INFO ("Type %d: converted to caps \"%s\"", i, str); + g_free (str); + + gst_caps_unref (caps); + } + else + GST_INFO ("Failed to convert type to GstCaps"); + + DeleteMediaType (type); + } + GST_INFO ("Enumeration complete"); + + enumerator->Release(); +} + +static void +dump_all_pin_media_types (IBaseFilter *filter) +{ + IEnumPins *enumpins = NULL; + IPin *pin = NULL; + HRESULT hres; + + hres = filter->EnumPins (&enumpins); + if (FAILED(hres)) { + GST_WARNING ("Cannot enumerate pins on filter"); + return; + } + + GST_INFO ("Enumerating pins on filter %p", filter); + while (enumpins->Next (1, &pin, NULL) == S_OK) + { + IMemInputPin *meminputpin; + PIN_DIRECTION pindir; + hres = pin->QueryDirection (&pindir); + + GST_INFO ("Found a pin with direction: %s", (pindir == PINDIR_INPUT)? "input": "output"); + dump_available_media_types (pin); + + hres = pin->QueryInterface ( + IID_IMemInputPin, (void **) &meminputpin); + if (hres == S_OK) { + GST_INFO ("Pin is a MemInputPin (push mode): %p", meminputpin); + meminputpin->Release(); + } + else + GST_INFO ("Pin is not a MemInputPin (pull mode?): %p", pin); + + pin->Release(); + } + enumpins->Release(); +} + +gboolean +gst_dshow_get_pin_from_filter (IBaseFilter *filter, PIN_DIRECTION pindir, IPin **pin) +{ + gboolean ret = FALSE; + IEnumPins *enumpins = NULL; + IPin *pintmp = NULL; + HRESULT hres; + *pin = NULL; + + hres = filter->EnumPins (&enumpins); + if (FAILED(hres)) { + return ret; + } + + while (enumpins->Next (1, &pintmp, NULL) == S_OK) + { + PIN_DIRECTION pindirtmp; + hres = pintmp->QueryDirection (&pindirtmp); + if (hres == S_OK && pindir == pindirtmp) { + *pin = pintmp; + ret = TRUE; + break; + } + pintmp->Release (); + } + enumpins->Release (); + + return ret; +} + +/* WNDPROC for application-supplied windows */ +LRESULT APIENTRY WndProcHook (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + /* Handle certain actions specially on the window passed to us. + * Then forward back to the original window. + */ + GstDshowVideoSink *sink = (GstDshowVideoSink *)GetProp (hWnd, L"GstDShowVideoSink"); + + switch (message) { + case WM_PAINT: + sink->renderersupport->PaintWindow (); + break; + case WM_MOVE: + case WM_SIZE: + sink->renderersupport->MoveWindow (); + break; + case WM_DISPLAYCHANGE: + sink->renderersupport->DisplayModeChanged(); + break; + case WM_ERASEBKGND: + /* DirectShow docs recommend ignoring this message to avoid flicker */ + return TRUE; + case WM_CLOSE: + sink->window_closed = TRUE; + } + return CallWindowProc (sink->prevWndProc, hWnd, message, wParam, lParam); +} + +/* WndProc for our default window, if the application didn't supply one */ +LRESULT APIENTRY +WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + GstDshowVideoSink *sink = (GstDshowVideoSink *)GetWindowLongPtr (hWnd, GWLP_USERDATA); + + if (!sink) { + /* I think these happen before we have a chance to set our userdata pointer */ + GST_DEBUG ("No sink!"); + return DefWindowProc (hWnd, message, wParam, lParam); + } + + GST_DEBUG_OBJECT (sink, "Got a window message for %x, %x", hWnd, message); + + switch (message) { + case WM_PAINT: + sink->renderersupport->PaintWindow (); + break; + case WM_MOVE: + case WM_SIZE: + sink->renderersupport->MoveWindow (); + break; + case WM_DISPLAYCHANGE: + sink->renderersupport->DisplayModeChanged(); + break; + case WM_ERASEBKGND: + /* DirectShow docs recommend ignoring this message */ + return TRUE; + case WM_CLOSE: + sink->renderersupport->DestroyWindow (); + sink->window_closed = TRUE; + return 0; + } + + return DefWindowProc (hWnd, message, wParam, lParam); +} + +static gpointer +gst_dshowvideosink_window_thread (GstDshowVideoSink * sink) +{ + WNDCLASS WndClass; + int width, height; + int offx, offy; + DWORD exstyle, style; + + memset (&WndClass, 0, sizeof (WNDCLASS)); + WndClass.style = CS_HREDRAW | CS_VREDRAW; + WndClass.hInstance = GetModuleHandle (NULL); + WndClass.lpszClassName = L"GST-DShowSink"; + WndClass.hbrBackground = (HBRUSH) GetStockObject (BLACK_BRUSH); + WndClass.cbClsExtra = 0; + WndClass.cbWndExtra = 0; + WndClass.lpfnWndProc = WndProc; + WndClass.hCursor = LoadCursor (NULL, IDC_ARROW); + RegisterClass (&WndClass); + + if (sink->full_screen) { + /* This doesn't seem to work, it returns the wrong values! But when we + * later use ShowWindow to show it maximized, it goes to full-screen + * anyway. TODO: Figure out why. */ + width = GetSystemMetrics (SM_CXFULLSCREEN); + height = GetSystemMetrics (SM_CYFULLSCREEN); + offx = 0; + offy = 0; + + style = WS_POPUP; /* No window decorations */ + exstyle = 0; + } + else { + /* By default, create a normal top-level window, the size + * of the video. + */ + RECT rect; + VIDEOINFOHEADER *vi = (VIDEOINFOHEADER *)sink->mediatype.pbFormat; + + /* rcTarget is the aspect-ratio-corrected size of the video. */ + width = vi->rcTarget.right + GetSystemMetrics (SM_CXSIZEFRAME) * 2; + height = vi->rcTarget.bottom + GetSystemMetrics (SM_CYCAPTION) + + (GetSystemMetrics (SM_CYSIZEFRAME) * 2); + + SystemParametersInfo (SPI_GETWORKAREA, NULL, &rect, 0); + int screenwidth = rect.right - rect.left; + int screenheight = rect.bottom - rect.top; + offx = rect.left; + offy = rect.top; + + /* Make it fit into the screen without changing the + * aspect ratio. */ + if (width > screenwidth) { + double ratio = (double)screenwidth/(double)width; + width = screenwidth; + height = (int)(height * ratio); + } + if (height > screenheight) { + double ratio = (double)screenheight/(double)height; + height = screenheight; + width = (int)(width * ratio); + } + + style = WS_OVERLAPPEDWINDOW; /* Normal top-level window */ + exstyle = 0; + } + + HWND video_window = CreateWindowEx (exstyle, L"GST-DShowSink", + L"GStreamer DirectShow sink default window", + style, offx, offy, width, height, NULL, NULL, + WndClass.hInstance, NULL); + if (video_window == NULL) { + GST_ERROR_OBJECT (sink, "Failed to create window!"); + return NULL; + } + + SetWindowLongPtr (video_window, GWLP_USERDATA, (LONG)sink); + + /* signal application we created a window */ + gst_x_overlay_got_xwindow_id (GST_X_OVERLAY (sink), + (gulong)video_window); + + /* Set the renderer's clipping window */ + if (!sink->renderersupport->SetRendererWindow (video_window)) { + GST_WARNING_OBJECT (sink, "Failed to set video clipping window on filter %p", sink->renderersupport); + } + + /* Now show the window, as appropriate */ + if (sink->full_screen) { + ShowWindow (video_window, SW_SHOWMAXIMIZED); + ShowCursor (FALSE); + } + else + ShowWindow (video_window, SW_SHOWNORMAL); + + /* Trigger the initial paint of the window */ + UpdateWindow (video_window); + + ReleaseSemaphore (sink->window_created_signal, 1, NULL); + + /* start message loop processing our default window messages */ + while (1) { + MSG msg; + + if (GetMessage (&msg, video_window, 0, 0) <= 0) { + GST_LOG_OBJECT (sink, "our window received WM_QUIT or error."); + break; + } + DispatchMessage (&msg); + } + + return NULL; +} + +static gboolean +gst_dshowvideosink_create_default_window (GstDshowVideoSink * sink) +{ + sink->window_created_signal = CreateSemaphore (NULL, 0, 1, NULL); + if (sink->window_created_signal == NULL) + goto failed; + + sink->window_thread = g_thread_create ( + (GThreadFunc) gst_dshowvideosink_window_thread, sink, TRUE, NULL); + + /* wait maximum 10 seconds for window to be created */ + if (WaitForSingleObject (sink->window_created_signal, + 10000) != WAIT_OBJECT_0) + goto failed; + + CloseHandle (sink->window_created_signal); + return TRUE; + +failed: + CloseHandle (sink->window_created_signal); + GST_ELEMENT_ERROR (sink, RESOURCE, WRITE, + ("Error creating our default window"), (NULL)); + + return FALSE; +} + +static void gst_dshowvideosink_set_window_id (GstXOverlay * overlay, ULONG window_id) +{ + GstDshowVideoSink *sink = GST_DSHOWVIDEOSINK (overlay); + HWND videowindow = (HWND)window_id; + + if (videowindow == sink->window_id) { + GST_DEBUG_OBJECT (sink, "Window already set"); + return; + } + + /* TODO: What if we already have a window? What if we're already playing? */ + sink->window_id = videowindow; +} + +static void gst_dshowvideosink_set_window_for_renderer (GstDshowVideoSink *sink) +{ + /* Application has requested a specific window ID */ + sink->prevWndProc = (WNDPROC) SetWindowLong (sink->window_id, GWL_WNDPROC, (LONG)WndProcHook); + GST_DEBUG_OBJECT (sink, "Set wndproc to %p from %p", WndProcHook, sink->prevWndProc); + SetProp (sink->window_id, L"GstDShowVideoSink", sink); + /* This causes the new WNDPROC to become active */ + SetWindowPos (sink->window_id, 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); + + if (!sink->renderersupport->SetRendererWindow (sink->window_id)) { + GST_WARNING_OBJECT (sink, "Failed to set HWND %x on renderer", sink->window_id); + return; + } + + /* This tells the renderer where the window is located, needed to + * start drawing in the right place. */ + sink->renderersupport->MoveWindow(); + GST_INFO_OBJECT (sink, "Set renderer window to %x", sink->window_id); +} + +static void +gst_dshowvideosink_prepare_window (GstDshowVideoSink *sink) +{ + /* Give the app a last chance to supply a window id */ + if (!sink->window_id) { + gst_x_overlay_prepare_xwindow_id (GST_X_OVERLAY (sink)); + } + + /* If the app supplied one, use it. Otherwise, go ahead + * and create (and use) our own window */ + if (sink->window_id) { + gst_dshowvideosink_set_window_for_renderer (sink); + } + else { + gst_dshowvideosink_create_default_window (sink); + } +} + +static gboolean +gst_dshowvideosink_connect_graph (GstDshowVideoSink *sink) +{ + HRESULT hres; + IPin *srcpin; + IPin *sinkpin; + + GST_INFO_OBJECT (sink, "Connecting DirectShow pins"); + + srcpin = sink->fakesrc->GetOutputPin(); + + gst_dshow_get_pin_from_filter (sink->renderersupport->GetFilter(), PINDIR_INPUT, + &sinkpin); + if (!sinkpin) { + GST_WARNING_OBJECT (sink, "Cannot get input pin from Renderer"); + return FALSE; + } + + /* Be warned that this call WILL deadlock unless you call it from + * the main thread. Thus, we call this from the state change, not from + * setcaps (which happens in a streaming thread). + */ + hres = sink->filter_graph->ConnectDirect ( + srcpin, sinkpin, NULL); + if (FAILED (hres)) { + GST_WARNING_OBJECT (sink, "Could not connect pins: %x", hres); + sinkpin->Release(); + return FALSE; + } + sinkpin->Release(); + return TRUE; +} + +static GstStateChangeReturn +gst_dshowvideosink_start_graph (GstDshowVideoSink *sink) +{ + IMediaControl *control = NULL; + HRESULT hres; + GstStateChangeReturn ret; + + GST_DEBUG_OBJECT (sink, "Connecting and starting DirectShow graph"); + + if (!sink->connected) { + /* This is fine; this just means we haven't connected yet. + * That's normal for the first time this is called. + * So, create a window (or start using an application-supplied + * one, then connect the graph */ + gst_dshowvideosink_prepare_window (sink); + if (!gst_dshowvideosink_connect_graph (sink)) { + ret = GST_STATE_CHANGE_FAILURE; + goto done; + } + sink->connected = TRUE; + } + + hres = sink->filter_graph->QueryInterface( + IID_IMediaControl, (void **) &control); + + if (FAILED (hres)) { + GST_WARNING_OBJECT (sink, "Failed to get IMediaControl interface"); + ret = GST_STATE_CHANGE_FAILURE; + goto done; + } + + GST_INFO_OBJECT (sink, "Running DirectShow graph"); + hres = control->Run(); + if (FAILED (hres)) { + GST_ERROR_OBJECT (sink, + "Failed to run the directshow graph (error=%x)", hres); + ret = GST_STATE_CHANGE_FAILURE; + goto done; + } + + GST_DEBUG_OBJECT (sink, "DirectShow graph is now running"); + ret = GST_STATE_CHANGE_SUCCESS; + +done: + if (control) + control->Release(); + + return ret; +} +static GstStateChangeReturn +gst_dshowvideosink_pause_graph (GstDshowVideoSink *sink) +{ + IMediaControl *control = NULL; + GstStateChangeReturn ret; + HRESULT hres; + + hres = sink->filter_graph->QueryInterface( + IID_IMediaControl, (void **) &control); + if (FAILED (hres)) { + GST_WARNING_OBJECT (sink, "Failed to get IMediaControl interface"); + ret = GST_STATE_CHANGE_FAILURE; + goto done; + } + + GST_INFO_OBJECT (sink, "Pausing DirectShow graph"); + hres = control->Pause(); + if (FAILED (hres)) { + GST_WARNING_OBJECT (sink, + "Can't pause the directshow graph (error=%x)", hres); + ret = GST_STATE_CHANGE_FAILURE; + goto done; + } + + ret = GST_STATE_CHANGE_SUCCESS; + +done: + if (control) + control->Release(); + + return ret; +} + +static GstStateChangeReturn +gst_dshowvideosink_stop_graph (GstDshowVideoSink *sink) +{ + IMediaControl *control = NULL; + GstStateChangeReturn ret; + HRESULT hres; + IPin *sinkpin; + + hres = sink->filter_graph->QueryInterface( + IID_IMediaControl, (void **) &control); + if (FAILED (hres)) { + GST_WARNING_OBJECT (sink, "Failed to get IMediaControl interface"); + ret = GST_STATE_CHANGE_FAILURE; + goto done; + } + + GST_INFO_OBJECT (sink, "Stopping DirectShow graph"); + hres = control->Stop(); + if (FAILED (hres)) { + GST_WARNING_OBJECT (sink, + "Can't stop the directshow graph (error=%x)", hres); + ret = GST_STATE_CHANGE_FAILURE; + goto done; + } + + sink->filter_graph->Disconnect(sink->fakesrc->GetOutputPin()); + + gst_dshow_get_pin_from_filter (sink->renderersupport->GetFilter(), PINDIR_INPUT, + &sinkpin); + sink->filter_graph->Disconnect(sinkpin); + sinkpin->Release(); + + GST_DEBUG_OBJECT (sink, "DirectShow graph has stopped"); + + if (sink->window_id) { + /* Return control of application window */ + SetWindowLong (sink->window_id, GWL_WNDPROC, (LONG)sink->prevWndProc); + RemoveProp (sink->window_id, L"GstDShowVideoSink"); + SetWindowPos (sink->window_id, 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); + sink->prevWndProc = NULL; + } + sink->connected = FALSE; + + ret = GST_STATE_CHANGE_SUCCESS; + +done: + if (control) + control->Release(); + + return ret; +} + +static GstStateChangeReturn +gst_dshowvideosink_change_state (GstElement * element, GstStateChange transition) +{ + GstDshowVideoSink *sink = GST_DSHOWVIDEOSINK (element); + GstStateChangeReturn ret; + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + ret = gst_dshowvideosink_start_graph (sink); + if (ret != GST_STATE_CHANGE_SUCCESS) + return ret; + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + ret = gst_dshowvideosink_pause_graph (sink); + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + ret = gst_dshowvideosink_stop_graph (sink); + break; + case GST_STATE_CHANGE_READY_TO_NULL: + gst_dshowvideosink_clear (sink); + break; + } + + return ret; +} + +class VMR9Support : public RendererSupport +{ +private: + GstDshowVideoSink *sink; + IBaseFilter *filter; + IVMRWindowlessControl9 *control; + IVMRFilterConfig9 *config; + HWND video_window; + +public: + VMR9Support (GstDshowVideoSink *sink) : + sink(sink), + filter(NULL), + control(NULL), + config(NULL) + { + } + + ~VMR9Support() { + if (control) + control->Release(); + if (config) + config->Release(); + if (filter) + filter->Release(); + } + + const char *GetName() { + return "VideoMixingRenderer9"; + } + + IBaseFilter *GetFilter() { + return filter; + } + + gboolean Configure() { + HRESULT hres; + + hres = CoCreateInstance (CLSID_VideoMixingRenderer9, NULL, CLSCTX_INPROC, + IID_IBaseFilter, (LPVOID *) &filter); + if (FAILED (hres)) { + GST_ERROR_OBJECT (sink, + "Can't create an instance of renderer (error=%x)", + hres); + return FALSE; + } + + hres = filter->QueryInterface ( + IID_IVMRFilterConfig9, (void **) &config); + if (FAILED (hres)) { + GST_WARNING_OBJECT (sink, "VMR9 filter config interface missing: %x", hres); + return FALSE; + } + + hres = config->SetRenderingMode (VMR9Mode_Windowless); + if (FAILED (hres)) { + GST_WARNING_OBJECT (sink, "VMR9 couldn't be set to windowless mode: %x", hres); + return FALSE; + } + else { + GST_DEBUG_OBJECT (sink, "Set VMR9 (%p) to windowless mode!", filter); + } + + /* We can't QI to this until _after_ we've been set to windowless mode. + * Apparently this is against the rules in COM, but that's how it is... */ + hres = filter->QueryInterface ( + IID_IVMRWindowlessControl9, (void **) &control); + if (FAILED (hres)) { + GST_WARNING_OBJECT (sink, "VMR9 windowless control interface missing: %x", hres); + return FALSE; + } + + if (sink->keep_aspect_ratio) { + control->SetAspectRatioMode(VMR9ARMode_LetterBox); + } + else { + control->SetAspectRatioMode(VMR9ARMode_None); + } + return TRUE; + } + + gboolean SetRendererWindow(HWND window) { + video_window = window; + HRESULT hres = control->SetVideoClippingWindow (video_window); + if (FAILED (hres)) { + GST_WARNING_OBJECT (sink, "Failed to set video clipping window on filter %p: %x", filter, hres); + return FALSE; + } + return TRUE; + } + + void PaintWindow() + { + HRESULT hr; + PAINTSTRUCT ps; + HDC hdc; + RECT rcClient; + + GetClientRect(video_window, &rcClient); + hdc = BeginPaint(video_window, &ps); + + hr = control->RepaintVideo(video_window, hdc); + + EndPaint(video_window, &ps); + } + + void MoveWindow() + { + HRESULT hr; + RECT rect; + + // Track the movement of the container window and resize as needed + GetClientRect(video_window, &rect); + hr = control->SetVideoPosition(NULL, &rect); + } + + void DisplayModeChanged() { + control->DisplayModeChanged(); + } + + void DestroyWindow() { + ::DestroyWindow (video_window); + } +}; + +class VMR7Support : public RendererSupport +{ +private: + GstDshowVideoSink *sink; + IBaseFilter *filter; + IVMRWindowlessControl *control; + IVMRFilterConfig *config; + HWND video_window; + +public: + VMR7Support (GstDshowVideoSink *sink) : + sink(sink), + filter(NULL), + control(NULL), + config(NULL) + { + } + + ~VMR7Support() { + if (control) + control->Release(); + if (config) + config->Release(); + if (filter) + filter->Release(); + } + + const char *GetName() { + return "VideoMixingRenderer"; + } + + IBaseFilter *GetFilter() { + return filter; + } + + gboolean Configure() { + HRESULT hres; + + hres = CoCreateInstance (CLSID_VideoMixingRenderer, NULL, CLSCTX_INPROC, + IID_IBaseFilter, (LPVOID *) &filter); + if (FAILED (hres)) { + GST_ERROR_OBJECT (sink, + "Can't create an instance of renderer (error=%x)", + hres); + return FALSE; + } + + hres = filter->QueryInterface ( + IID_IVMRFilterConfig, (void **) &config); + if (FAILED (hres)) { + GST_WARNING_OBJECT (sink, "VMR filter config interface missing: %x", hres); + return FALSE; + } + + hres = config->SetRenderingMode (VMRMode_Windowless); + if (FAILED (hres)) { + GST_WARNING_OBJECT (sink, "VMR couldn't be set to windowless mode: %x", hres); + return FALSE; + } + else { + GST_DEBUG_OBJECT (sink, "Set VMR (%p) to windowless mode!", filter); + } + + hres = filter->QueryInterface ( + IID_IVMRWindowlessControl, (void **) &control); + if (FAILED (hres)) { + GST_WARNING_OBJECT (sink, "VMR windowless control interface missing: %x", hres); + return FALSE; + } + + if (sink->keep_aspect_ratio) { + control->SetAspectRatioMode(VMR_ARMODE_LETTER_BOX); + } + else { + control->SetAspectRatioMode(VMR_ARMODE_NONE); + } + return TRUE; + } + + gboolean SetRendererWindow(HWND window) { + video_window = window; + HRESULT hres = control->SetVideoClippingWindow (video_window); + if (FAILED (hres)) { + GST_WARNING_OBJECT (sink, "Failed to set video clipping window on filter %p: %x", filter, hres); + return FALSE; + } + return TRUE; + } + + void PaintWindow() + { + HRESULT hr; + PAINTSTRUCT ps; + HDC hdc; + RECT rcClient; + + GetClientRect(video_window, &rcClient); + hdc = BeginPaint(video_window, &ps); + + hr = control->RepaintVideo(video_window, hdc); + + EndPaint(video_window, &ps); + } + + void MoveWindow() + { + HRESULT hr; + RECT rect; + + // Track the movement of the container window and resize as needed + GetClientRect(video_window, &rect); + hr = control->SetVideoPosition(NULL, &rect); + } + + void DisplayModeChanged() { + control->DisplayModeChanged(); + } + + void DestroyWindow() { + ::DestroyWindow (video_window); + } +}; + +static gboolean +gst_dshowvideosink_create_renderer (GstDshowVideoSink *sink) +{ + GST_DEBUG_OBJECT (sink, "Trying to create renderer '%s'", "VMR9"); + + RendererSupport *support = NULL; + + if (sink->preferredrenderer) { + if (!strcmp (sink->preferredrenderer, "VMR9")) { + GST_INFO_OBJECT (sink, "Forcing use of VMR9"); + support = new VMR9Support (sink); + } + else if (!strcmp (sink->preferredrenderer, "VMR")) { + GST_INFO_OBJECT (sink, "Forcing use of VMR"); + support = new VMR7Support (sink); + } + else { + GST_ERROR_OBJECT (sink, "Unknown sink type '%s'", sink->preferredrenderer); + return FALSE; + } + + if (!support->Configure()) { + GST_ERROR_OBJECT (sink, "Couldn't configure selected renderer"); + delete support; + return FALSE; + } + goto done; + } + + support = new VMR9Support (sink); + if (!support->Configure()) { + GST_INFO_OBJECT (sink, "Failed to configure VMR9, trying VMR7"); + delete support; + support = new VMR7Support (sink); + if (!support->Configure()) { + GST_ERROR_OBJECT (sink, "Failed to configure VMR9 or VMR7"); + delete support; + return FALSE; + } + } + +done: + sink->renderersupport = support; + return TRUE; +} + +static gboolean +gst_dshowvideosink_build_filtergraph (GstDshowVideoSink *sink) +{ + HRESULT hres; + + /* Build our DirectShow FilterGraph, looking like: + * + * [ fakesrc ] -> [ sink filter ] + * + * so we can feed data in through the fakesrc. + * + * The sink filter can be one of our supported filters: VMR9 (VMR7?, EMR?) + */ + + hres = CoCreateInstance (CLSID_FilterGraph, NULL, CLSCTX_INPROC, + IID_IFilterGraph, (LPVOID *) & sink->filter_graph); + if (FAILED (hres)) { + GST_ERROR_OBJECT (sink, + "Can't create an instance of the dshow graph manager (error=%x)", hres); + goto error; + } + + sink->fakesrc = new VideoFakeSrc(); + + IBaseFilter *filter; + hres = sink->fakesrc->QueryInterface ( + IID_IBaseFilter, (void **) &filter); + if (FAILED (hres)) { + GST_ERROR_OBJECT (sink, "Could not QI fakesrc to IBaseFilter"); + goto error; + } + + hres = sink->filter_graph->AddFilter (filter, L"fakesrc"); + if (FAILED (hres)) { + GST_ERROR_OBJECT (sink, + "Can't add our fakesrc filter to the graph (error=%x)", hres); + goto error; + } + + if (!gst_dshowvideosink_create_renderer (sink)) { + GST_ERROR_OBJECT (sink, "Could not create a video renderer"); + goto error; + } + + /* dump_all_pin_media_types (sink->renderer); */ + + hres = + sink->filter_graph->AddFilter (sink->renderersupport->GetFilter(), + L"renderer"); + if (FAILED (hres)) { + GST_ERROR_OBJECT (sink, + "Can't add renderer to the graph (error=%x)", hres); + goto error; + } + + return TRUE; + +error: + if (sink->fakesrc) { + sink->fakesrc->Release(); + sink->fakesrc = NULL; + } + + if (sink->filter_graph) { + sink->filter_graph->Release(); + sink->filter_graph = NULL; + } + + return FALSE; +} + +static gboolean +gst_dshowvideosink_start (GstBaseSink * bsink) +{ + HRESULT hres = S_FALSE; + GstDshowVideoSink *sink = GST_DSHOWVIDEOSINK (bsink); + + /* Just build the filtergraph; we don't link or otherwise configure it yet */ + return gst_dshowvideosink_build_filtergraph (sink); +} + +static gboolean +gst_dshowvideosink_set_caps (GstBaseSink * bsink, GstCaps * caps) +{ + GstDshowVideoSink *sink = GST_DSHOWVIDEOSINK (bsink); + + /* TODO: What do we want to do if the caps change while we're running? + * Find out if we can handle this or not... */ + + if (!gst_caps_to_directshow_media_type (caps, &sink->mediatype)) { + GST_WARNING_OBJECT (sink, "Cannot convert caps to AM_MEDIA_TYPE, rejecting"); + return FALSE; + } + + /* Now we have an AM_MEDIA_TYPE describing what we're going to send. + * We set this on our DirectShow fakesrc's output pin. + */ + sink->fakesrc->GetOutputPin()->SetMediaType (&sink->mediatype); + + return TRUE; +} + +static gboolean +gst_dshowvideosink_stop (GstBaseSink * bsink) +{ + IPin *input_pin = NULL, *output_pin = NULL; + HRESULT hres = S_FALSE; + GstDshowVideoSink *sink = GST_DSHOWVIDEOSINK (bsink); + + if (!sink->filter_graph) { + GST_WARNING_OBJECT (sink, "Cannot destroy filter graph; it doesn't exist"); + return TRUE; + } + + /* Release the renderer */ + if (sink->renderersupport) { + delete sink->renderersupport; + sink->renderersupport = NULL; + } + + /* Release our dshow fakesrc */ + if (sink->fakesrc) { + sink->fakesrc->Release(); + sink->fakesrc = NULL; + } + + /* Release the filter graph manager */ + if (sink->filter_graph) { + sink->filter_graph->Release(); + sink->filter_graph = NULL; + } + + return TRUE; +} + +static GstFlowReturn +gst_dshowvideosink_render (GstBaseSink *bsink, GstBuffer *buffer) +{ + GstDshowVideoSink *sink = GST_DSHOWVIDEOSINK (bsink); + GstFlowReturn ret; + + if (sink->window_closed) { + GST_WARNING_OBJECT (sink, "Window has been closed, stopping"); + return GST_FLOW_ERROR; + } + + GST_DEBUG_OBJECT (sink, "Pushing buffer through fakesrc->renderer"); + ret = sink->fakesrc->GetOutputPin()->PushBuffer (buffer); + GST_DEBUG_OBJECT (sink, "Done pushing buffer through fakesrc->renderer"); + + return ret; +} + +/* TODO: How can we implement these? Figure that out... */ +static gboolean +gst_dshowvideosink_unlock (GstBaseSink * bsink) +{ + GstDshowVideoSink *sink = GST_DSHOWVIDEOSINK (bsink); + + return TRUE; +} + +static gboolean +gst_dshowvideosink_unlock_stop (GstBaseSink * bsink) +{ + GstDshowVideoSink *sink = GST_DSHOWVIDEOSINK (bsink); + + return TRUE; +} + +/* TODO: Move all of this into generic code? */ + +/* Helpers to format GUIDs the same way we find them in the source */ +#define GUID_FORMAT "{%.8x, %.4x, %.4x, { %.2x, %.2x, %.2x, %.2x, %.2x, %.2x, %.2x, %.2x }}" +#define GUID_ARGS(guid) \ + guid.Data1, guid.Data2, guid.Data3, \ + guid.Data4[0], guid.Data4[1], guid.Data4[3], guid.Data4[4], \ + guid.Data4[5], guid.Data4[6], guid.Data4[7], guid.Data4[8] + +static GstCaps * +audio_media_type_to_caps (AM_MEDIA_TYPE *mediatype) +{ + return NULL; +} + +static GstCaps * +video_media_type_to_caps (AM_MEDIA_TYPE *mediatype) +{ + GstCaps *caps = NULL; + + /* TODO: Add RGB types. */ + if (IsEqualGUID (mediatype->subtype, MEDIASUBTYPE_YUY2)) + caps = gst_caps_new_simple ("video/x-raw-yuv", + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC ('Y', 'U', 'Y', '2'), NULL); + else if (IsEqualGUID (mediatype->subtype, MEDIASUBTYPE_UYVY)) + caps = gst_caps_new_simple ("video/x-raw-yuv", + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'), NULL); + else if (IsEqualGUID (mediatype->subtype, MEDIASUBTYPE_YUYV)) + caps = gst_caps_new_simple ("video/x-raw-yuv", + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC ('Y', 'U', 'Y', 'V'), NULL); + else if (IsEqualGUID (mediatype->subtype, MEDIASUBTYPE_YV12)) + caps = gst_caps_new_simple ("video/x-raw-yuv", + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC ('Y', 'V', '1', '2'), NULL); + + if (!caps) { + GST_DEBUG ("No subtype known; cannot continue"); + return NULL; + } + + if (IsEqualGUID (mediatype->formattype, FORMAT_VideoInfo) && + mediatype->cbFormat >= sizeof(VIDEOINFOHEADER)) + { + VIDEOINFOHEADER *vh = (VIDEOINFOHEADER *)mediatype->pbFormat; + + /* TODO: Set PAR here. Based on difference between source and target RECTs? + * Do we want framerate? Based on AvgTimePerFrame? */ + gst_caps_set_simple (caps, + "width", G_TYPE_INT, vh->bmiHeader.biWidth, + "height", G_TYPE_INT, vh->bmiHeader.biHeight, + NULL); + } + + return caps; +} + + +/* Create a GstCaps object representing the same media type as + * this AM_MEDIA_TYPE. + * + * Returns NULL if no corresponding GStreamer type is known. + * + * May modify mediatype. + */ +static GstCaps * +gst_directshow_media_type_to_caps (AM_MEDIA_TYPE *mediatype) +{ + GstCaps *caps = NULL; + + if (IsEqualGUID (mediatype->majortype, MEDIATYPE_Video)) + caps = video_media_type_to_caps (mediatype); + else if (IsEqualGUID (mediatype->majortype, MEDIATYPE_Audio)) + caps = audio_media_type_to_caps (mediatype); + else { + GST_DEBUG ("Non audio/video media types not yet recognised, please add me: " + GUID_FORMAT, GUID_ARGS(mediatype->majortype)); + } + + if (caps) { + gchar *capsstring = gst_caps_to_string (caps); + GST_DEBUG ("Converted AM_MEDIA_TYPE to \"%s\"", capsstring); + g_free (capsstring); + } + else { + GST_WARNING ("Failed to convert AM_MEDIA_TYPE to caps"); + } + + return caps; +} + +/* Fill in a DirectShow AM_MEDIA_TYPE structure representing the same media + * type as this GstCaps object. + * + * Returns FALSE if no corresponding type is known. + * + * Only operates on simple (single structure) caps. + */ +static gboolean +gst_caps_to_directshow_media_type (GstCaps *caps, AM_MEDIA_TYPE *mediatype) +{ + GstStructure *s = gst_caps_get_structure (caps, 0); + const gchar *name = gst_structure_get_name (s); + + gchar *capsstring = gst_caps_to_string (caps); + GST_DEBUG ("Converting caps \"%s\" to AM_MEDIA_TYPE", capsstring); + g_free (capsstring); + + memset (mediatype, 0, sizeof (AM_MEDIA_TYPE)); + + if (!strcmp (name, "video/x-raw-yuv")) { + guint32 fourcc; + int width, height; + int bpp; + + if (!gst_structure_get_fourcc (s, "format", &fourcc)) { + GST_WARNING ("Failed to convert caps, no fourcc"); + return FALSE; + } + + if (!gst_structure_get_int (s, "width", &width)) { + GST_WARNING ("Failed to convert caps, no width"); + return FALSE; + } + if (!gst_structure_get_int (s, "height", &height)) { + GST_WARNING ("Failed to convert caps, no height"); + return FALSE; + } + + mediatype->majortype = MEDIATYPE_Video; + switch (fourcc) { + case GST_MAKE_FOURCC ('Y', 'U', 'Y', '2'): + mediatype->subtype = MEDIASUBTYPE_YUY2; + bpp = 16; + break; + case GST_MAKE_FOURCC ('Y', 'U', 'Y', 'V'): + mediatype->subtype = MEDIASUBTYPE_YUYV; + bpp = 16; + break; + case GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'): + mediatype->subtype = MEDIASUBTYPE_UYVY; + bpp = 16; + break; + case GST_MAKE_FOURCC ('Y', 'V', '1', '2'): + mediatype->subtype = MEDIASUBTYPE_YV12; + bpp = 12; + break; + default: + GST_WARNING ("Failed to convert caps, not a known fourcc"); + return FALSE; + } + + mediatype->bFixedSizeSamples = TRUE; /* Always true for raw video */ + mediatype->bTemporalCompression = FALSE; /* Likewise, always false */ + + { + int par_n, par_d; + VIDEOINFOHEADER *vi = (VIDEOINFOHEADER *)CoTaskMemAlloc (sizeof (VIDEOINFOHEADER)); + memset (vi, 0, sizeof (VIDEOINFOHEADER)); + + mediatype->formattype = FORMAT_VideoInfo; + mediatype->cbFormat = sizeof (VIDEOINFOHEADER); + mediatype->pbFormat = (BYTE *)vi; + + mediatype->lSampleSize = width * height * bpp / 8; + + GST_INFO ("Set mediatype format: size %d, sample size %d", mediatype->cbFormat, mediatype->lSampleSize); + + vi->rcSource.top = 0; + vi->rcSource.left = 0; + vi->rcSource.bottom = height; + vi->rcSource.right = width; + + vi->rcTarget.top = 0; + vi->rcTarget.left = 0; + if (gst_structure_get_fraction (s, "pixel-aspect-ratio", &par_n, &par_d)) { + /* To handle non-square pixels, we set the target rectangle to a + * different size than the source rectangle. + * There might be a better way, but this seems to work. */ + vi->rcTarget.bottom = height; + vi->rcTarget.right = width * par_n / par_d; + GST_DEBUG ("Got PAR: set target right to %d from width %d", vi->rcTarget.right, width); + } + else { + GST_DEBUG ("No PAR found"); + vi->rcTarget.bottom = height; + vi->rcTarget.right = width; + } + + vi->bmiHeader.biSize = sizeof (BITMAPINFOHEADER); + vi->bmiHeader.biWidth = width; + vi->bmiHeader.biHeight = -height; /* Required to be negative. */ + vi->bmiHeader.biPlanes = 1; /* Required to be 1 */ + vi->bmiHeader.biBitCount = bpp; + vi->bmiHeader.biCompression = fourcc; + vi->bmiHeader.biSizeImage = width * height * bpp / 8; + + /* We can safely zero these; they don't matter for our uses */ + vi->bmiHeader.biXPelsPerMeter = 0; + vi->bmiHeader.biYPelsPerMeter = 0; + vi->bmiHeader.biClrUsed = 0; + vi->bmiHeader.biClrImportant = 0; + } + + GST_DEBUG ("Successfully built AM_MEDIA_TYPE from caps"); + return TRUE; + } + + GST_WARNING ("Failed to convert caps, not a known caps type"); + /* Only YUV supported so far */ + + return FALSE; +} + +/* Plugin entry point */ +extern "C" static gboolean +plugin_init (GstPlugin * plugin) +{ + /* PRIMARY: this is the best videosink to use on windows */ + if (!gst_element_register (plugin, "dshowvideosink", + GST_RANK_PRIMARY, GST_TYPE_DSHOWVIDEOSINK)) + return FALSE; + + return TRUE; +} + +extern "C" GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "dshowsinkwrapper", + "DirectShow sink wrapper plugin", + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/sys/dshowvideosink/dshowvideosink.h b/sys/dshowvideosink/dshowvideosink.h new file mode 100644 index 00000000..a1a35d11 --- /dev/null +++ b/sys/dshowvideosink/dshowvideosink.h @@ -0,0 +1,104 @@ +/* GStreamer + * Copyright (C) 2008 Michael Smith + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __DSHOWVIDEOSINK_H__ +#define __DSHOWVIDEOSINK_H__ + +#include +#include + +#include "dshowvideofakesrc.h" + +#include + +#include "d3d9.h" +#include "vmr9.h" + +#pragma warning( disable : 4090 4024) + +G_BEGIN_DECLS +#define GST_TYPE_DSHOWVIDEOSINK (gst_dshowvideosink_get_type()) +#define GST_DSHOWVIDEOSINK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_DSHOWVIDEOSINK,GstDshowVideoSink)) +#define GST_DSHOWVIDEOSINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_DSHOWVIDEOSINK,GstDshowVideoSinkClass)) +#define GST_IS_DSHOWVIDEOSINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_DSHOWVIDEOSINK)) +#define GST_IS_DSHOWVIDEOSINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_DSHOWVIDEOSINK)) +typedef struct _GstDshowVideoSink GstDshowVideoSink; +typedef struct _GstDshowVideoSinkClass GstDshowVideoSinkClass; + +/* Renderer-specific support classes */ +class RendererSupport +{ +public: + virtual const char *GetName() = 0; + virtual IBaseFilter *GetFilter() = 0; + virtual gboolean Configure() = 0; + virtual gboolean SetRendererWindow(HWND window) = 0; + virtual void PaintWindow() = 0; + virtual void MoveWindow() = 0; + virtual void DestroyWindow() = 0; + virtual void DisplayModeChanged() = 0; +}; + +struct _GstDshowVideoSink +{ + GstBaseSink sink; + + /* Preferred renderer to use: VM9 or VMR */ + char *preferredrenderer; + + /* The filter graph (DirectShow equivalent to pipeline */ + IFilterGraph *filter_graph; + + /* Renderer wrapper (EVR, VMR9, or VMR) and support code */ + RendererSupport *renderersupport; + + /* Our fakesrc filter */ + VideoFakeSrc *fakesrc; + + /* DirectShow description of media type (equivalent of GstCaps) */ + AM_MEDIA_TYPE mediatype; + + gboolean keep_aspect_ratio; + gboolean full_screen; + + /* If the window is closed, we set this and error out */ + gboolean window_closed; + + /* The video window set through GstXOverlay */ + HWND window_id; + + gboolean connected; + + /* If we create our own window, we run it from another thread */ + GThread *window_thread; + HANDLE window_created_signal; + + /* If we use an app-supplied window, we need to hook its WNDPROC */ + WNDPROC prevWndProc; +}; + +struct _GstDshowVideoSinkClass +{ + GstBaseSinkClass parent_class; +}; + +GType gst_dshowvideosink_get_type (void); + +G_END_DECLS +#endif /* __DSHOWVIDEOSINK_H__ */ -- cgit v1.2.1