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.
This commit is contained in:
Michael Smith 2009-02-04 17:50:51 -08:00
parent e3fcf51e2c
commit 2e401cc71d
4 changed files with 2045 additions and 1833 deletions

View File

@ -19,13 +19,16 @@
#include "dshowvideofakesrc.h" #include "dshowvideofakesrc.h"
GST_DEBUG_CATEGORY_EXTERN (dshowvideosink_debug);
#define GST_CAT_DEFAULT dshowvideosink_debug
// {A0A5CF33-BD0C-4158-9A56-3011DEE3AF6B} // {A0A5CF33-BD0C-4158-9A56-3011DEE3AF6B}
const GUID CLSID_VideoFakeSrc = const GUID CLSID_VideoFakeSrc =
{ 0xa0a5cf33, 0xbd0c, 0x4158, { 0x9a, 0x56, 0x30, 0x11, 0xde, 0xe3, 0xaf, 0x6b } }; { 0xa0a5cf33, 0xbd0c, 0x4158, { 0x9a, 0x56, 0x30, 0x11, 0xde, 0xe3, 0xaf, 0x6b } };
/* output pin*/ /* output pin*/
VideoFakeSrcPin::VideoFakeSrcPin (CBaseFilter *pFilter, CCritSec *sec, HRESULT *hres): VideoFakeSrcPin::VideoFakeSrcPin (CBaseFilter *pFilter, CCritSec *sec, HRESULT *hres):
CBaseOutputPin("VideoFakeSrcPin", pFilter, sec, hres, L"output") CDynamicOutputPin("VideoFakeSrcPin", pFilter, sec, hres, L"output")
{ {
} }
@ -101,7 +104,8 @@ HRESULT VideoFakeSrcPin::DecideBufferSize (IMemAllocator *pAlloc, ALLOCATOR_PROP
properties.cbPrefix, properties.cBuffers); properties.cbPrefix, properties.cBuffers);
/* Then actually allocate the buffers */ /* Then actually allocate the buffers */
pAlloc->Commit(); hres = pAlloc->Commit();
GST_DEBUG ("Allocator commit returned %x", hres);
return S_OK; return S_OK;
} }
@ -199,20 +203,81 @@ STDMETHODIMP VideoFakeSrcPin::CopyToDestinationBuffer (byte *srcbuf, byte *dstbu
return S_OK; 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) GstFlowReturn VideoFakeSrcPin::PushBuffer(GstBuffer *buffer)
{ {
IMediaSample *pSample = NULL; IMediaSample *pSample = NULL;
byte *data = GST_BUFFER_DATA (buffer); byte *data = GST_BUFFER_DATA (buffer);
int attempts = 0;
/* TODO: Use more of the arguments here? */ HRESULT hres;
HRESULT hres = GetDeliveryBuffer(&pSample, NULL, NULL, 0);
if (SUCCEEDED (hres))
{
BYTE *sample_buffer; BYTE *sample_buffer;
AM_MEDIA_TYPE *mediatype; 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->GetPointer(&sample_buffer);
pSample->GetMediaType(&mediatype); pSample->GetMediaType(&mediatype);
if (mediatype) if (mediatype)
@ -236,20 +301,28 @@ GstFlowReturn VideoFakeSrcPin::PushBuffer(GstBuffer *buffer)
* this at a higher level, inside BaseSink. */ * this at a higher level, inside BaseSink. */
pSample->SetTime(NULL, NULL); pSample->SetTime(NULL, NULL);
while (attempts < MAX_ATTEMPTS)
{
hres = Deliver(pSample); hres = Deliver(pSample);
if (SUCCEEDED (hres))
break;
attempts++;
Sleep(100);
}
pSample->Release(); pSample->Release();
StopUsingOutputPin();
if (SUCCEEDED (hres)) if (SUCCEEDED (hres))
return GST_FLOW_OK; return GST_FLOW_OK;
else if (hres == VFW_E_NOT_CONNECTED) else {
GST_WARNING_OBJECT (this, "Failed to deliver sample: %x", hres);
if (hres == VFW_E_NOT_CONNECTED)
return GST_FLOW_NOT_LINKED; return GST_FLOW_NOT_LINKED;
else else
return GST_FLOW_ERROR; return GST_FLOW_ERROR;
} }
else {
GST_WARNING ("Could not get sample for delivery to sink: %x", hres);
return GST_FLOW_ERROR;
}
} }
STDMETHODIMP VideoFakeSrcPin::Flush () STDMETHODIMP VideoFakeSrcPin::Flush ()
@ -259,7 +332,8 @@ STDMETHODIMP VideoFakeSrcPin::Flush ()
return S_OK; return S_OK;
} }
VideoFakeSrc::VideoFakeSrc() : CBaseFilter("VideoFakeSrc", NULL, &m_critsec, CLSID_VideoFakeSrc) VideoFakeSrc::VideoFakeSrc() : CBaseFilter("VideoFakeSrc", NULL, &m_critsec, CLSID_VideoFakeSrc),
m_evFilterStoppingEvent(TRUE)
{ {
HRESULT hr = S_OK; HRESULT hr = S_OK;
m_pOutputPin = new VideoFakeSrcPin ((CSource *)this, &m_critsec, &hr); m_pOutputPin = new VideoFakeSrcPin ((CSource *)this, &m_critsec, &hr);
@ -279,3 +353,62 @@ VideoFakeSrcPin *VideoFakeSrc::GetOutputPin()
{ {
return m_pOutputPin; 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;
}

View File

@ -22,7 +22,7 @@
#include <streams.h> #include <streams.h>
#include <gst/gst.h> #include <gst/gst.h>
class VideoFakeSrcPin : public CBaseOutputPin class VideoFakeSrcPin : public CDynamicOutputPin
{ {
protected: protected:
/* members */ /* members */
@ -41,11 +41,14 @@ public:
virtual HRESULT CheckMediaType(const CMediaType *pmt); virtual HRESULT CheckMediaType(const CMediaType *pmt);
HRESULT GetMediaType(int iPosition, CMediaType *pMediaType); HRESULT GetMediaType(int iPosition, CMediaType *pMediaType);
virtual HRESULT DecideBufferSize (IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *ppropInputRequest); 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 (SetMediaType) (AM_MEDIA_TYPE *pmt);
STDMETHOD (Flush) (); STDMETHOD (Flush) ();
STDMETHODIMP Notify(IBaseFilter * pSender, Quality q); STDMETHODIMP Notify(IBaseFilter * pSender, Quality q);
STDMETHODIMP Disconnect();
STDMETHODIMP Block(DWORD dwBlockFlags, HANDLE hEvent);
}; };
class VideoFakeSrc : public CBaseFilter class VideoFakeSrc : public CBaseFilter
@ -55,6 +58,8 @@ private:
CCritSec m_critsec; CCritSec m_critsec;
VideoFakeSrcPin *m_pOutputPin; VideoFakeSrcPin *m_pOutputPin;
CAMEvent m_evFilterStoppingEvent;
public: public:
/* methods */ /* methods */
VideoFakeSrc (void); VideoFakeSrc (void);
@ -65,6 +70,11 @@ public:
/* Overrides */ /* Overrides */
int GetPinCount(); int GetPinCount();
CBasePin *GetPin(int n); CBasePin *GetPin(int n);
STDMETHODIMP Run(REFERENCE_TIME tStart);
STDMETHODIMP Stop(void);
STDMETHODIMP Pause(void);
STDMETHODIMP JoinFilterGraph(IFilterGraph* pGraph, LPCWSTR pName);
}; };
#endif /* __DSHOWVIDEOFAKESRC_H__ */ #endif /* __DSHOWVIDEOFAKESRC_H__ */

View File

@ -28,13 +28,15 @@
#include "windows.h" #include "windows.h"
#define WM_GRAPH_NOTIFY WM_APP + 1 /* Private message */
static const GstElementDetails gst_dshowvideosink_details = static const GstElementDetails gst_dshowvideosink_details =
GST_ELEMENT_DETAILS ("DirectShow video sink", GST_ELEMENT_DETAILS ("DirectShow video sink",
"Sink/Video", "Sink/Video",
"Display data using a DirectShow video renderer", "Display data using a DirectShow video renderer",
"Pioneers of the Inevitable <songbird@songbirdnest.com>"); "Pioneers of the Inevitable <songbird@songbirdnest.com>");
GST_DEBUG_CATEGORY_STATIC (dshowvideosink_debug); GST_DEBUG_CATEGORY (dshowvideosink_debug);
#define GST_CAT_DEFAULT dshowvideosink_debug #define GST_CAT_DEFAULT dshowvideosink_debug
static GstCaps * gst_directshow_media_type_to_caps (AM_MEDIA_TYPE *mediatype); static GstCaps * gst_directshow_media_type_to_caps (AM_MEDIA_TYPE *mediatype);
@ -198,6 +200,7 @@ gst_dshowvideosink_clear (GstDshowVideoSink *sink)
sink->renderersupport = NULL; sink->renderersupport = NULL;
sink->fakesrc = NULL; sink->fakesrc = NULL;
sink->filter_graph = NULL; sink->filter_graph = NULL;
sink->filter_media_event = NULL;
sink->keep_aspect_ratio = FALSE; sink->keep_aspect_ratio = FALSE;
sink->full_screen = FALSE; sink->full_screen = FALSE;
@ -396,6 +399,21 @@ gst_dshow_get_pin_from_filter (IBaseFilter *filter, PIN_DIRECTION pindir, IPin *
return ret; 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, &param1, &param2, 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 */ /* WNDPROC for application-supplied windows */
LRESULT APIENTRY WndProcHook (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) LRESULT APIENTRY WndProcHook (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{ {
@ -405,6 +423,9 @@ LRESULT APIENTRY WndProcHook (HWND hWnd, UINT message, WPARAM wParam, LPARAM lPa
GstDshowVideoSink *sink = (GstDshowVideoSink *)GetProp (hWnd, L"GstDShowVideoSink"); GstDshowVideoSink *sink = (GstDshowVideoSink *)GetProp (hWnd, L"GstDShowVideoSink");
switch (message) { switch (message) {
case WM_GRAPH_NOTIFY:
gst_dshowvideosink_handle_event (sink);
return 0;
case WM_PAINT: case WM_PAINT:
sink->renderersupport->PaintWindow (); sink->renderersupport->PaintWindow ();
break; break;
@ -436,9 +457,13 @@ WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
return DefWindowProc (hWnd, message, wParam, lParam); return DefWindowProc (hWnd, message, wParam, lParam);
} }
GST_DEBUG_OBJECT (sink, "Got a window message for %x, %x", hWnd, message); //GST_DEBUG_OBJECT (sink, "Got a window message for %x, %x", hWnd, message);
switch (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: case WM_PAINT:
sink->renderersupport->PaintWindow (); sink->renderersupport->PaintWindow ();
break; break;
@ -538,6 +563,8 @@ gst_dshowvideosink_window_thread (GstDshowVideoSink * sink)
SetWindowLongPtr (video_window, GWLP_USERDATA, (LONG)sink); SetWindowLongPtr (video_window, GWLP_USERDATA, (LONG)sink);
sink->window_id = video_window;
/* signal application we created a window */ /* signal application we created a window */
gst_x_overlay_got_xwindow_id (GST_X_OVERLAY (sink), gst_x_overlay_got_xwindow_id (GST_X_OVERLAY (sink),
(gulong)video_window); (gulong)video_window);
@ -637,6 +664,8 @@ static void gst_dshowvideosink_set_window_for_renderer (GstDshowVideoSink *sink)
static void static void
gst_dshowvideosink_prepare_window (GstDshowVideoSink *sink) gst_dshowvideosink_prepare_window (GstDshowVideoSink *sink)
{ {
HRESULT hres;
/* Give the app a last chance to supply a window id */ /* Give the app a last chance to supply a window id */
if (!sink->window_id) { if (!sink->window_id) {
gst_x_overlay_prepare_xwindow_id (GST_X_OVERLAY (sink)); gst_x_overlay_prepare_xwindow_id (GST_X_OVERLAY (sink));
@ -650,6 +679,23 @@ gst_dshowvideosink_prepare_window (GstDshowVideoSink *sink)
else { else {
gst_dshowvideosink_create_default_window (sink); 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 static gboolean
@ -1152,6 +1198,11 @@ static gboolean
gst_dshowvideosink_build_filtergraph (GstDshowVideoSink *sink) gst_dshowvideosink_build_filtergraph (GstDshowVideoSink *sink)
{ {
HRESULT hres; HRESULT hres;
gboolean comInit = FALSE;
hres = CoInitialize(0);
if (SUCCEEDED (hres))
comInit = TRUE;
/* Build our DirectShow FilterGraph, looking like: /* Build our DirectShow FilterGraph, looking like:
* *
@ -1203,6 +1254,8 @@ gst_dshowvideosink_build_filtergraph (GstDshowVideoSink *sink)
goto error; goto error;
} }
if (comInit)
CoUninitialize();
return TRUE; return TRUE;
error: error:
@ -1216,6 +1269,14 @@ error:
sink->filter_graph = NULL; sink->filter_graph = NULL;
} }
if (sink->filter_media_event) {
sink->filter_media_event->Release();
sink->filter_media_event = NULL;
}
if (comInit)
CoUninitialize();
return FALSE; return FALSE;
} }
@ -1234,18 +1295,24 @@ gst_dshowvideosink_set_caps (GstBaseSink * bsink, GstCaps * caps)
{ {
GstDshowVideoSink *sink = GST_DSHOWVIDEOSINK (bsink); GstDshowVideoSink *sink = GST_DSHOWVIDEOSINK (bsink);
/* TODO: What do we want to do if the caps change while we're running? if (sink->connected) {
* Find out if we can handle this or not... */ /* 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)) { if (!gst_caps_to_directshow_media_type (caps, &sink->mediatype)) {
GST_WARNING_OBJECT (sink, "Cannot convert caps to AM_MEDIA_TYPE, rejecting"); GST_WARNING_OBJECT (sink, "Cannot convert caps to AM_MEDIA_TYPE, rejecting");
return FALSE; 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. /* Now we have an AM_MEDIA_TYPE describing what we're going to send.
* We set this on our DirectShow fakesrc's output pin. * We set this on our DirectShow fakesrc's output pin.
*/ */
sink->fakesrc->GetOutputPin()->SetMediaType (&sink->mediatype); sink->fakesrc->GetOutputPin()->SetMediaType (&sink->mediatype);
GST_DEBUG_OBJECT (sink, "Configured output pin media type");
return TRUE; return TRUE;
} }
@ -1296,7 +1363,7 @@ gst_dshowvideosink_render (GstBaseSink *bsink, GstBuffer *buffer)
GST_DEBUG_OBJECT (sink, "Pushing buffer through fakesrc->renderer"); GST_DEBUG_OBJECT (sink, "Pushing buffer through fakesrc->renderer");
ret = sink->fakesrc->GetOutputPin()->PushBuffer (buffer); ret = sink->fakesrc->GetOutputPin()->PushBuffer (buffer);
GST_DEBUG_OBJECT (sink, "Done pushing buffer through fakesrc->renderer"); GST_DEBUG_OBJECT (sink, "Done pushing buffer through fakesrc->renderer: %s", gst_flow_get_name(ret));
return ret; return ret;
} }

View File

@ -65,6 +65,8 @@ struct _GstDshowVideoSink
/* The filter graph (DirectShow equivalent to pipeline */ /* The filter graph (DirectShow equivalent to pipeline */
IFilterGraph *filter_graph; IFilterGraph *filter_graph;
IMediaEventEx *filter_media_event;
/* Renderer wrapper (EVR, VMR9, or VMR) and support code */ /* Renderer wrapper (EVR, VMR9, or VMR) and support code */
RendererSupport *renderersupport; RendererSupport *renderersupport;