| /* GStreamer |
| * Copyright (C) 2008 Pioneers of the Inevitable <songbird@songbirdnest.com> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, 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); |
| |
| 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; |
| GstMapInfo map; |
| int attempts = 0; |
| HRESULT hres; |
| BYTE *sample_buffer; |
| AM_MEDIA_TYPE *mediatype; |
| |
| /* FIXME: check return value. */ |
| gst_buffer_map (buffer, &map, GST_MAP_READ); |
| data = map.data; |
| 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); |
| } |
| gst_buffer_unmap (buffer, &map); |
| |
| 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; |
| } |
| |