1 /* GStreamer
2  * Copyright (C) 2008 Pioneers of the Inevitable <songbird@songbirdnest.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 
20 #include "dshowvideofakesrc.h"
21 
22 GST_DEBUG_CATEGORY_EXTERN (dshowvideosink_debug);
23 #define GST_CAT_DEFAULT dshowvideosink_debug
24 
25 // {A0A5CF33-BD0C-4158-9A56-3011DEE3AF6B}
26 const GUID CLSID_VideoFakeSrc =
27 { 0xa0a5cf33, 0xbd0c, 0x4158, { 0x9a, 0x56, 0x30, 0x11, 0xde, 0xe3, 0xaf, 0x6b } };
28 
29 /* output pin*/
VideoFakeSrcPin(CBaseFilter * pFilter,CCritSec * sec,HRESULT * hres)30 VideoFakeSrcPin::VideoFakeSrcPin (CBaseFilter *pFilter, CCritSec *sec, HRESULT *hres):
31   CDynamicOutputPin("VideoFakeSrcPin", pFilter, sec, hres, L"output")
32 {
33 }
34 
~VideoFakeSrcPin()35 VideoFakeSrcPin::~VideoFakeSrcPin()
36 {
37 }
38 
GetMediaType(int iPosition,CMediaType * pMediaType)39 HRESULT VideoFakeSrcPin::GetMediaType(int iPosition, CMediaType *pMediaType)
40 {
41   GST_DEBUG ("GetMediaType(%d) called", iPosition);
42   if(iPosition == 0) {
43     *pMediaType = m_MediaType;
44     return S_OK;
45   }
46 
47   return VFW_S_NO_MORE_ITEMS;
48 }
49 
50 /* This seems to be called to notify us of the actual media type being used,
51  * even though SetMediaType isn't called. How bizarre! */
CheckMediaType(const CMediaType * pmt)52 HRESULT VideoFakeSrcPin::CheckMediaType(const CMediaType *pmt)
53 {
54     GST_DEBUG ("CheckMediaType called: %p", pmt);
55 
56     /* The video renderer will request a different stride, which we must accept.
57      * So, we accept arbitrary strides (and do memcpy() to convert if needed),
58      * and require the rest of the media type to match
59      */
60     if (IsEqualGUID(pmt->majortype,m_MediaType.majortype) &&
61         IsEqualGUID(pmt->subtype,m_MediaType.subtype) &&
62         IsEqualGUID(pmt->formattype,m_MediaType.formattype) &&
63         pmt->cbFormat >= m_MediaType.cbFormat)
64     {
65       if (IsEqualGUID(pmt->formattype, FORMAT_VideoInfo)) {
66         VIDEOINFOHEADER *newvh = (VIDEOINFOHEADER *)pmt->pbFormat;
67         VIDEOINFOHEADER *curvh = (VIDEOINFOHEADER *)m_MediaType.pbFormat;
68 
69         if ((memcmp ((void *)&newvh->rcSource, (void *)&curvh->rcSource, sizeof (RECT)) == 0) &&
70             (memcmp ((void *)&newvh->rcTarget, (void *)&curvh->rcTarget, sizeof (RECT)) == 0) &&
71             (newvh->bmiHeader.biCompression == curvh->bmiHeader.biCompression) &&
72             (newvh->bmiHeader.biHeight == curvh->bmiHeader.biHeight) &&
73             (newvh->bmiHeader.biWidth >= curvh->bmiHeader.biWidth))
74         {
75           GST_DEBUG ("CheckMediaType has same media type, width %d (%d image)", newvh->bmiHeader.biWidth, curvh->bmiHeader.biWidth);
76 
77           /* OK, compatible! */
78           return S_OK;
79         }
80         else {
81           GST_WARNING ("Looked similar, but aren't...");
82         }
83       }
84 
85     }
86     GST_WARNING ("Different media types, FAILING!");
87     return S_FALSE;
88 }
89 
DecideBufferSize(IMemAllocator * pAlloc,ALLOCATOR_PROPERTIES * ppropInputRequest)90 HRESULT VideoFakeSrcPin::DecideBufferSize (IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *ppropInputRequest)
91 {
92   ALLOCATOR_PROPERTIES properties;
93   GST_DEBUG ("Required allocator properties: %d, %d, %d, %d",
94         ppropInputRequest->cbAlign, ppropInputRequest->cbBuffer,
95         ppropInputRequest->cbPrefix, ppropInputRequest->cBuffers);
96 
97   ppropInputRequest->cbBuffer = m_SampleSize;
98   ppropInputRequest->cBuffers = 1;
99 
100   /* First set the buffer descriptions we're interested in */
101   HRESULT hres = pAlloc->SetProperties(ppropInputRequest, &properties);
102   GST_DEBUG ("Actual Allocator properties: %d, %d, %d, %d",
103         properties.cbAlign, properties.cbBuffer,
104         properties.cbPrefix, properties.cBuffers);
105 
106   return S_OK;
107 }
108 
109 STDMETHODIMP
Notify(IBaseFilter * pSender,Quality q)110 VideoFakeSrcPin::Notify(IBaseFilter * pSender, Quality q)
111 {
112   /* Implementing this usefully is not required, but the base class
113    * has an assertion here... */
114   /* TODO: Map this to GStreamer QOS events? */
115   return E_NOTIMPL;
116 }
117 
SetMediaType(AM_MEDIA_TYPE * pmt)118 STDMETHODIMP VideoFakeSrcPin::SetMediaType (AM_MEDIA_TYPE *pmt)
119 {
120   m_MediaType.Set (*pmt);
121   m_SampleSize = m_MediaType.GetSampleSize();
122 
123   GST_DEBUG ("SetMediaType called. SampleSize is %d", m_SampleSize);
124 
125   return S_OK;
126 }
127 
128 /* If the destination buffer is a different shape (strides, etc.) from the source
129  * buffer, we have to copy. Do that here, for supported video formats.
130  *
131  * TODO: When possible (when these things DON'T differ), we should buffer-alloc the
132  * final output buffer, and not do this copy */
CopyToDestinationBuffer(byte * srcbuf,byte * dstbuf)133 STDMETHODIMP VideoFakeSrcPin::CopyToDestinationBuffer (byte *srcbuf, byte *dstbuf)
134 {
135   VIDEOINFOHEADER *vh = (VIDEOINFOHEADER *)m_MediaType.pbFormat;
136   GST_DEBUG ("Rendering a frame");
137 
138   byte *src, *dst;
139   int dststride, srcstride, rows;
140   guint32 fourcc = vh->bmiHeader.biCompression;
141 
142   /* biHeight is always negative; we don't want that. */
143   int height = ABS (vh->bmiHeader.biHeight);
144   int width = vh->bmiHeader.biWidth;
145 
146   /* YUY2 is the preferred layout for DirectShow, so we will probably get this
147    * most of the time */
148   if ((fourcc == GST_MAKE_FOURCC ('Y', 'U', 'Y', '2')) ||
149       (fourcc == GST_MAKE_FOURCC ('Y', 'U', 'Y', 'V')) ||
150       (fourcc == GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y')))
151   {
152     /* Nice and simple */
153     int srcstride = GST_ROUND_UP_4 (vh->rcSource.right * 2);
154     int dststride = width * 2;
155 
156     for (int i = 0; i < height; i++) {
157       memcpy (dstbuf + dststride * i, srcbuf + srcstride * i, srcstride);
158     }
159   }
160   else if (fourcc == GST_MAKE_FOURCC ('Y', 'V', '1', '2')) {
161     for (int component = 0; component < 3; component++) {
162       // TODO: Get format properly rather than hard-coding it. Use gst_video_* APIs *?
163       if (component == 0) {
164         srcstride = GST_ROUND_UP_4 (vh->rcSource.right);
165         src = srcbuf;
166       }
167       else {
168         srcstride = GST_ROUND_UP_4 ( GST_ROUND_UP_2 (vh->rcSource.right) / 2);
169         if (component == 1)
170           src = srcbuf + GST_ROUND_UP_4 (vh->rcSource.right) * GST_ROUND_UP_2 (vh->rcSource.bottom);
171         else
172           src = srcbuf + GST_ROUND_UP_4 (vh->rcSource.right) * GST_ROUND_UP_2 (vh->rcSource.bottom) +
173                   srcstride * (GST_ROUND_UP_2 (vh->rcSource.bottom) / 2);
174       }
175 
176       /* Is there a better way to do this? This is ICK! */
177       if (component == 0) {
178         dststride = width;
179         dst = dstbuf;
180         rows = height;
181       } else if (component == 1) {
182         dststride = width / 2;
183         dst = dstbuf + width * height;
184         rows = height/2;
185       }
186       else {
187         dststride = width / 2;
188         dst = dstbuf + width * height +
189                        width/2 * height/2;
190         rows = height/2;
191       }
192 
193       for (int i = 0; i < rows; i++) {
194         memcpy (dst + i * dststride, src + i * srcstride, srcstride);
195       }
196     }
197   }
198 
199   return S_OK;
200 }
201 
Disconnect()202 STDMETHODIMP VideoFakeSrcPin::Disconnect ()
203 {
204   GST_DEBUG_OBJECT (this, "Disconnecting pin");
205   HRESULT hr = CDynamicOutputPin::Disconnect();
206   GST_DEBUG_OBJECT (this, "Pin disconnected");
207   return hr;
208 }
209 
Inactive()210 HRESULT VideoFakeSrcPin::Inactive ()
211 {
212   GST_DEBUG_OBJECT (this, "Pin going inactive");
213   HRESULT hr = CDynamicOutputPin::Inactive();
214   GST_DEBUG_OBJECT (this, "Pin inactivated");
215   return hr;
216 }
217 
BreakConnect()218 HRESULT VideoFakeSrcPin::BreakConnect ()
219 {
220   GST_DEBUG_OBJECT (this, "Breaking connection");
221   HRESULT hr = CDynamicOutputPin::BreakConnect();
222   GST_DEBUG_OBJECT (this, "Connection broken");
223   return hr;
224 }
225 
CompleteConnect(IPin * pReceivePin)226 HRESULT VideoFakeSrcPin::CompleteConnect (IPin *pReceivePin)
227 {
228   GST_DEBUG_OBJECT (this, "Completing connection");
229   HRESULT hr = CDynamicOutputPin::CompleteConnect(pReceivePin);
230   GST_DEBUG_OBJECT (this, "Completed connection: %x", hr);
231   return hr;
232 }
233 
Block(DWORD dwBlockFlags,HANDLE hEvent)234 STDMETHODIMP VideoFakeSrcPin::Block(DWORD dwBlockFlags, HANDLE hEvent)
235 {
236   GST_DEBUG_OBJECT (this, "Calling Block()");
237   HRESULT hr = CDynamicOutputPin::Block (dwBlockFlags, hEvent);
238   GST_DEBUG_OBJECT (this, "Called Block()");
239   return hr;
240 }
241 
242 /* When moving the video to a different monitor, directshow stops and restarts the playback pipeline.
243  * Unfortunately, it doesn't properly block pins or do anything special, so we racily just fail
244  * at this point.
245  * So, we try multiple times in a loop, hoping that it'll have finished (we get no notifications at all!)
246  * at some point.
247  */
248 #define MAX_ATTEMPTS 10
249 
PushBuffer(GstBuffer * buffer)250 GstFlowReturn VideoFakeSrcPin::PushBuffer(GstBuffer *buffer)
251 {
252   IMediaSample *pSample = NULL;
253   byte *data;
254   GstMapInfo map;
255   int attempts = 0;
256   HRESULT hres;
257   BYTE *sample_buffer;
258   AM_MEDIA_TYPE *mediatype;
259 
260   /* FIXME: check return value. */
261   gst_buffer_map (buffer, &map, GST_MAP_READ);
262   data = map.data;
263   StartUsingOutputPin();
264 
265   while (attempts < MAX_ATTEMPTS)
266   {
267     hres = GetDeliveryBuffer(&pSample, NULL, NULL, 0);
268     if (SUCCEEDED (hres))
269       break;
270     attempts++;
271     Sleep(100);
272   }
273 
274   if (FAILED (hres))
275   {
276     StopUsingOutputPin();
277     GST_WARNING ("Could not get sample for delivery to sink: %x", hres);
278     return GST_FLOW_ERROR;
279   }
280 
281   pSample->GetPointer(&sample_buffer);
282   pSample->GetMediaType(&mediatype);
283   if (mediatype)
284     SetMediaType (mediatype);
285 
286   if(sample_buffer)
287   {
288     /* Copy to the destination stride.
289      * This is not just a simple memcpy because of the different strides.
290      * TODO: optimise for the same-stride case and avoid the copy entirely.
291      */
292     CopyToDestinationBuffer (data, sample_buffer);
293   }
294   gst_buffer_unmap (buffer, &map);
295 
296   pSample->SetDiscontinuity(FALSE); /* Decoded frame; unimportant */
297   pSample->SetSyncPoint(TRUE); /* Decoded frame; always a valid syncpoint */
298   pSample->SetPreroll(FALSE); /* For non-displayed frames.
299                                  Not used in GStreamer */
300 
301   /* Disable synchronising on this sample. We instead let GStreamer handle
302    * this at a higher level, inside BaseSink. */
303   pSample->SetTime(NULL, NULL);
304 
305   while (attempts < MAX_ATTEMPTS)
306   {
307     hres = Deliver(pSample);
308     if (SUCCEEDED (hres))
309       break;
310     attempts++;
311     Sleep(100);
312   }
313 
314   pSample->Release();
315 
316   StopUsingOutputPin();
317 
318   if (SUCCEEDED (hres))
319     return GST_FLOW_OK;
320   else {
321     GST_WARNING_OBJECT (this, "Failed to deliver sample: %x", hres);
322     if (hres == VFW_E_NOT_CONNECTED)
323       return GST_FLOW_NOT_LINKED;
324     else
325       return GST_FLOW_ERROR;
326   }
327 }
328 
Flush()329 STDMETHODIMP VideoFakeSrcPin::Flush ()
330 {
331   DeliverBeginFlush();
332   DeliverEndFlush();
333   return S_OK;
334 }
335 
VideoFakeSrc()336 VideoFakeSrc::VideoFakeSrc() : CBaseFilter("VideoFakeSrc", NULL, &m_critsec, CLSID_VideoFakeSrc),
337 	m_evFilterStoppingEvent(TRUE)
338 {
339   HRESULT hr = S_OK;
340   m_pOutputPin = new VideoFakeSrcPin ((CSource *)this, &m_critsec, &hr);
341 }
342 
GetPinCount()343 int VideoFakeSrc::GetPinCount()
344 {
345   return 1;
346 }
347 
GetPin(int n)348 CBasePin *VideoFakeSrc::GetPin(int n)
349 {
350   return (CBasePin *)m_pOutputPin;
351 }
352 
GetOutputPin()353 VideoFakeSrcPin *VideoFakeSrc::GetOutputPin()
354 {
355   return m_pOutputPin;
356 }
357 
Stop(void)358 STDMETHODIMP VideoFakeSrc::Stop(void)
359 {
360   GST_DEBUG_OBJECT (this, "Stop()");
361   m_evFilterStoppingEvent.Set();
362 
363   return CBaseFilter::Stop();
364 }
365 
Pause(void)366 STDMETHODIMP VideoFakeSrc::Pause(void)
367 {
368   GST_DEBUG_OBJECT (this, "Pause()");
369 
370   m_evFilterStoppingEvent.Reset();
371 
372   return CBaseFilter::Pause();
373 }
374 
Run(REFERENCE_TIME tStart)375 STDMETHODIMP VideoFakeSrc::Run(REFERENCE_TIME tStart)
376 {
377   GST_DEBUG_OBJECT (this, "Run()");
378 
379   return CBaseFilter::Run(tStart);
380 }
381 
JoinFilterGraph(IFilterGraph * pGraph,LPCWSTR pName)382 STDMETHODIMP VideoFakeSrc::JoinFilterGraph(IFilterGraph* pGraph, LPCWSTR pName)
383 {
384   HRESULT hr;
385 
386   // The filter is joining the filter graph.
387   if(NULL != pGraph)
388   {
389     IGraphConfig* pGraphConfig = NULL;
390     hr = pGraph->QueryInterface(IID_IGraphConfig, (void**)&pGraphConfig);
391     if(FAILED(hr))
392       return hr;
393 
394     hr = CBaseFilter::JoinFilterGraph(pGraph, pName);
395     if(FAILED(hr))
396     {
397       pGraphConfig->Release();
398       return hr;
399     }
400 
401     m_pOutputPin->SetConfigInfo(pGraphConfig, m_evFilterStoppingEvent);
402     pGraphConfig->Release();
403   }
404   else
405   {
406     hr = CBaseFilter::JoinFilterGraph(pGraph, pName);
407     if(FAILED(hr))
408       return hr;
409 
410     m_pOutputPin->SetConfigInfo(NULL, NULL);
411   }
412 
413   return S_OK;
414 }
415 
416