From 2e401cc71da0607966e3b5f31608d326e9819b3e Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Wed, 4 Feb 2009 17:50:51 -0800 Subject: Handle many more edge cases in dshowvideosink. Instrument various codepaths with debug messages. Handle (as best as I could see how - it's pretty nasty) moving a video window to another monitor. Add listening for directshow events. --- sys/dshowvideosink/dshowvideofakesrc.cpp | 695 ++++--- sys/dshowvideosink/dshowvideofakesrc.h | 16 +- sys/dshowvideosink/dshowvideosink.cpp | 3165 +++++++++++++++--------------- sys/dshowvideosink/dshowvideosink.h | 2 + 4 files changed, 2045 insertions(+), 1833 deletions(-) (limited to 'sys') diff --git a/sys/dshowvideosink/dshowvideofakesrc.cpp b/sys/dshowvideosink/dshowvideofakesrc.cpp index 541e3885..a2de4088 100644 --- a/sys/dshowvideosink/dshowvideofakesrc.cpp +++ b/sys/dshowvideosink/dshowvideofakesrc.cpp @@ -1,281 +1,414 @@ -/* GStreamer - * Copyright (C) 2008 Pioneers of the Inevitable - * - * 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; -} +/* GStreamer + * Copyright (C) 2008 Pioneers of the Inevitable + * + * 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" + +GST_DEBUG_CATEGORY_EXTERN (dshowvideosink_debug); +#define GST_CAT_DEFAULT dshowvideosink_debug + +// {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): + CDynamicOutputPin("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 */ + hres = pAlloc->Commit(); + GST_DEBUG ("Allocator commit returned %x", hres); + + 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; +} + +STDMETHODIMP VideoFakeSrcPin::Disconnect () +{ + GST_DEBUG_OBJECT (this, "Disconnecting pin"); + HRESULT hr = CDynamicOutputPin::Disconnect(); + GST_DEBUG_OBJECT (this, "Pin disconnected"); + return hr; +} + +HRESULT VideoFakeSrcPin::Inactive () +{ + GST_DEBUG_OBJECT (this, "Pin going inactive"); + HRESULT hr = CDynamicOutputPin::Inactive(); + GST_DEBUG_OBJECT (this, "Pin inactivated"); + return hr; +} + +HRESULT VideoFakeSrcPin::BreakConnect () +{ + GST_DEBUG_OBJECT (this, "Breaking connection"); + HRESULT hr = CDynamicOutputPin::BreakConnect(); + GST_DEBUG_OBJECT (this, "Connection broken"); + return hr; +} + +HRESULT VideoFakeSrcPin::CompleteConnect (IPin *pReceivePin) +{ + GST_DEBUG_OBJECT (this, "Completing connection"); + HRESULT hr = CDynamicOutputPin::CompleteConnect(pReceivePin); + GST_DEBUG_OBJECT (this, "Completed connection: %x", hr); + return hr; +} + +STDMETHODIMP VideoFakeSrcPin::Block(DWORD dwBlockFlags, HANDLE hEvent) +{ + GST_DEBUG_OBJECT (this, "Calling Block()"); + HRESULT hr = CDynamicOutputPin::Block (dwBlockFlags, hEvent); + GST_DEBUG_OBJECT (this, "Called Block()"); + return hr; +} + +/* When moving the video to a different monitor, directshow stops and restarts the playback pipeline. + * Unfortunately, it doesn't properly block pins or do anything special, so we racily just fail + * at this point. + * So, we try multiple times in a loop, hoping that it'll have finished (we get no notifications at all!) + * at some point. + */ +#define MAX_ATTEMPTS 10 + +GstFlowReturn VideoFakeSrcPin::PushBuffer(GstBuffer *buffer) +{ + IMediaSample *pSample = NULL; + byte *data = GST_BUFFER_DATA (buffer); + int attempts = 0; + HRESULT hres; + BYTE *sample_buffer; + AM_MEDIA_TYPE *mediatype; + + StartUsingOutputPin(); + + while (attempts < MAX_ATTEMPTS) + { + hres = GetDeliveryBuffer(&pSample, NULL, NULL, 0); + if (SUCCEEDED (hres)) + break; + attempts++; + Sleep(100); + } + + if (FAILED (hres)) + { + StopUsingOutputPin(); + GST_WARNING ("Could not get sample for delivery to sink: %x", hres); + return GST_FLOW_ERROR; + } + + 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); + + while (attempts < MAX_ATTEMPTS) + { + hres = Deliver(pSample); + if (SUCCEEDED (hres)) + break; + attempts++; + Sleep(100); + } + + pSample->Release(); + + StopUsingOutputPin(); + + if (SUCCEEDED (hres)) + return GST_FLOW_OK; + else { + GST_WARNING_OBJECT (this, "Failed to deliver sample: %x", hres); + if (hres == VFW_E_NOT_CONNECTED) + return GST_FLOW_NOT_LINKED; + else + return GST_FLOW_ERROR; + } +} + +STDMETHODIMP VideoFakeSrcPin::Flush () +{ + DeliverBeginFlush(); + DeliverEndFlush(); + return S_OK; +} + +VideoFakeSrc::VideoFakeSrc() : CBaseFilter("VideoFakeSrc", NULL, &m_critsec, CLSID_VideoFakeSrc), + m_evFilterStoppingEvent(TRUE) +{ + 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; +} + +STDMETHODIMP VideoFakeSrc::Stop(void) +{ + GST_DEBUG_OBJECT (this, "Stop()"); + m_evFilterStoppingEvent.Set(); + + return CBaseFilter::Stop(); +} + +STDMETHODIMP VideoFakeSrc::Pause(void) +{ + GST_DEBUG_OBJECT (this, "Pause()"); + + m_evFilterStoppingEvent.Reset(); + + return CBaseFilter::Pause(); +} + +STDMETHODIMP VideoFakeSrc::Run(REFERENCE_TIME tStart) +{ + GST_DEBUG_OBJECT (this, "Run()"); + + return CBaseFilter::Run(tStart); +} + +STDMETHODIMP VideoFakeSrc::JoinFilterGraph(IFilterGraph* pGraph, LPCWSTR pName) +{ + HRESULT hr; + + // The filter is joining the filter graph. + if(NULL != pGraph) + { + IGraphConfig* pGraphConfig = NULL; + hr = pGraph->QueryInterface(IID_IGraphConfig, (void**)&pGraphConfig); + if(FAILED(hr)) + return hr; + + hr = CBaseFilter::JoinFilterGraph(pGraph, pName); + if(FAILED(hr)) + { + pGraphConfig->Release(); + return hr; + } + + m_pOutputPin->SetConfigInfo(pGraphConfig, m_evFilterStoppingEvent); + pGraphConfig->Release(); + } + else + { + hr = CBaseFilter::JoinFilterGraph(pGraph, pName); + if(FAILED(hr)) + return hr; + + m_pOutputPin->SetConfigInfo(NULL, NULL); + } + + return S_OK; +} + diff --git a/sys/dshowvideosink/dshowvideofakesrc.h b/sys/dshowvideosink/dshowvideofakesrc.h index a6b03243..ac2248da 100644 --- a/sys/dshowvideosink/dshowvideofakesrc.h +++ b/sys/dshowvideosink/dshowvideofakesrc.h @@ -22,7 +22,7 @@ #include #include -class VideoFakeSrcPin : public CBaseOutputPin +class VideoFakeSrcPin : public CDynamicOutputPin { protected: /* members */ @@ -41,11 +41,14 @@ public: virtual HRESULT CheckMediaType(const CMediaType *pmt); HRESULT GetMediaType(int iPosition, CMediaType *pMediaType); virtual HRESULT DecideBufferSize (IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *ppropInputRequest); + virtual HRESULT BreakConnect(); + virtual HRESULT CompleteConnect(IPin *pReceivePin); + virtual HRESULT Inactive(); STDMETHOD (SetMediaType) (AM_MEDIA_TYPE *pmt); STDMETHOD (Flush) (); STDMETHODIMP Notify(IBaseFilter * pSender, Quality q); - - + STDMETHODIMP Disconnect(); + STDMETHODIMP Block(DWORD dwBlockFlags, HANDLE hEvent); }; class VideoFakeSrc : public CBaseFilter @@ -55,6 +58,8 @@ private: CCritSec m_critsec; VideoFakeSrcPin *m_pOutputPin; + CAMEvent m_evFilterStoppingEvent; + public: /* methods */ VideoFakeSrc (void); @@ -65,6 +70,11 @@ public: /* Overrides */ int GetPinCount(); CBasePin *GetPin(int n); + + STDMETHODIMP Run(REFERENCE_TIME tStart); + STDMETHODIMP Stop(void); + STDMETHODIMP Pause(void); + STDMETHODIMP JoinFilterGraph(IFilterGraph* pGraph, LPCWSTR pName); }; #endif /* __DSHOWVIDEOFAKESRC_H__ */ diff --git a/sys/dshowvideosink/dshowvideosink.cpp b/sys/dshowvideosink/dshowvideosink.cpp index 788dbdd5..0745921c 100644 --- a/sys/dshowvideosink/dshowvideosink.cpp +++ b/sys/dshowvideosink/dshowvideosink.cpp @@ -1,1549 +1,1616 @@ -/* GStreamer - * Copyright (C) 2008 Pioneers of the Inevitable - * - * 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", - "Pioneers of the Inevitable "); - -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) -{ - HRESULT hr; - - gst_dshowvideosink_clear (sink); - - hr = CoInitialize (0); - if (SUCCEEDED(hr)) - sink->comInitialized = TRUE; - - /* 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); - - if (sink->comInitialized) { - CoUninitialize (); - sink->comInitialized = FALSE; - } - - 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) +/* GStreamer + * Copyright (C) 2008 Pioneers of the Inevitable + * + * 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" + +#define WM_GRAPH_NOTIFY WM_APP + 1 /* Private message */ + +static const GstElementDetails gst_dshowvideosink_details = +GST_ELEMENT_DETAILS ("DirectShow video sink", + "Sink/Video", + "Display data using a DirectShow video renderer", + "Pioneers of the Inevitable "); + +GST_DEBUG_CATEGORY (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->filter_media_event = 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) +{ + HRESULT hr; + + gst_dshowvideosink_clear (sink); + + hr = CoInitialize (0); + if (SUCCEEDED(hr)) + sink->comInitialized = TRUE; + + /* 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); + + if (sink->comInitialized) { + CoUninitialize (); + sink->comInitialized = FALSE; + } + + 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; +} + +static void +gst_dshowvideosink_handle_event (GstDshowVideoSink *sink) +{ + if (sink->filter_media_event) { + long evCode; + LONG_PTR param1, param2; + HRESULT hr; + while (SUCCEEDED (sink->filter_media_event->GetEvent(&evCode, ¶m1, ¶m2, 0))) + { + GST_INFO_OBJECT (sink, "Received DirectShow graph event code 0x%x", evCode); + sink->filter_media_event->FreeEventParams(evCode, param1, param2); + } + } +} + +/* 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_GRAPH_NOTIFY: + gst_dshowvideosink_handle_event (sink); + return 0; + 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_GRAPH_NOTIFY: + GST_LOG_OBJECT (sink, "GRAPH_NOTIFY WINDOW MESSAGE"); + gst_dshowvideosink_handle_event (sink); + return 0; + 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); + + sink->window_id = video_window; + + /* 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) +{ + HRESULT hres; + + /* 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); + } + + if (sink->filter_media_event) { + sink->filter_media_event->Release(); + sink->filter_media_event = NULL; + } + + hres = sink->filter_graph->QueryInterface( + IID_IMediaEventEx, (void **) &sink->filter_media_event); + + if (FAILED (hres)) { + GST_WARNING_OBJECT (sink, "Failed to get IMediaEventEx"); + } + else { + hres = sink->filter_media_event->SetNotifyWindow ((OAHWND)sink->window_id, + WM_GRAPH_NOTIFY, 0); + GST_DEBUG_OBJECT (sink, "SetNotifyWindow(%p) returned %x", sink->window_id, hres); + } +} + +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; + gboolean comInit = FALSE; + + hres = CoInitialize(0); + if (SUCCEEDED (hres)) + comInit = TRUE; + + /* 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; + } + + if (comInit) + CoUninitialize(); + return TRUE; + +error: + if (sink->fakesrc) { + sink->fakesrc->Release(); + sink->fakesrc = NULL; + } + + if (sink->filter_graph) { + sink->filter_graph->Release(); + sink->filter_graph = NULL; + } + + if (sink->filter_media_event) { + sink->filter_media_event->Release(); + sink->filter_media_event = NULL; + } + + if (comInit) + CoUninitialize(); + + 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); + + if (sink->connected) { + /* Look at the DShow APIs for dynamically modifying the pipeline and see + * if we can make this work later... */ + GST_WARNING_OBJECT (sink, "Changing caps at runtime is not yet supported"); + return FALSE; + } + + if (!gst_caps_to_directshow_media_type (caps, &sink->mediatype)) { + GST_WARNING_OBJECT (sink, "Cannot convert caps to AM_MEDIA_TYPE, rejecting"); + return FALSE; + } + + GST_DEBUG_OBJECT (sink, "Configuring output pin media type"); + /* 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); + GST_DEBUG_OBJECT (sink, "Configured output pin media type"); + + 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: %s", gst_flow_get_name(ret)); + + 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 index fd03c2b7..d5ea9e91 100644 --- a/sys/dshowvideosink/dshowvideosink.h +++ b/sys/dshowvideosink/dshowvideosink.h @@ -65,6 +65,8 @@ struct _GstDshowVideoSink /* The filter graph (DirectShow equivalent to pipeline */ IFilterGraph *filter_graph; + IMediaEventEx *filter_media_event; + /* Renderer wrapper (EVR, VMR9, or VMR) and support code */ RendererSupport *renderersupport; -- cgit v1.2.1