summaryrefslogtreecommitdiff
path: root/sys
diff options
context:
space:
mode:
Diffstat (limited to 'sys')
-rw-r--r--sys/Makefile.am3
-rw-r--r--sys/dshowvideosink/Makefile.am9
-rw-r--r--sys/dshowvideosink/README5
-rw-r--r--sys/dshowvideosink/dshowvideofakesrc.cpp281
-rw-r--r--sys/dshowvideosink/dshowvideofakesrc.h70
-rw-r--r--sys/dshowvideosink/dshowvideosink.cpp1542
-rw-r--r--sys/dshowvideosink/dshowvideosink.h104
7 files changed, 2013 insertions, 1 deletions
diff --git a/sys/Makefile.am b/sys/Makefile.am
index aa77cd6e0..5a7e2a043 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 000000000..1fa81b75b
--- /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 000000000..af296dc9a
--- /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 000000000..401941231
--- /dev/null
+++ b/sys/dshowvideosink/dshowvideofakesrc.cpp
@@ -0,0 +1,281 @@
+/* GStreamer
+ * Copyright (C) 2008 Michael Smith <msmith@songbirdnest.com>
+ *
+ * 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 000000000..afbb1bfe4
--- /dev/null
+++ b/sys/dshowvideosink/dshowvideofakesrc.h
@@ -0,0 +1,70 @@
+/* GStreamer
+ * Copyright (C) 2008 Michael Smith <msmith@songbirdnest.com>
+ *
+ * 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 <streams.h>
+#include <gst/gst.h>
+
+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 000000000..fb8ea16d5
--- /dev/null
+++ b/sys/dshowvideosink/dshowvideosink.cpp
@@ -0,0 +1,1542 @@
+/* GStreamer
+ * Copyright (C) 2008 Michael Smith <msmith@songbirdnest.com>
+ *
+ * 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 <gst/interfaces/xoverlay.h>
+
+#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 <msmith@songbirdnest.com>");
+
+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 000000000..a1a35d11c
--- /dev/null
+++ b/sys/dshowvideosink/dshowvideosink.h
@@ -0,0 +1,104 @@
+/* GStreamer
+ * Copyright (C) 2008 Michael Smith <msmith@songbirdnest.com>
+ *
+ * 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 <gst/gst.h>
+#include <gst/base/gstbasesink.h>
+
+#include "dshowvideofakesrc.h"
+
+#include <dshow.h>
+
+#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__ */