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"
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):
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);
/* Then actually allocate the buffers */
pAlloc->Commit();
hres = pAlloc->Commit();
GST_DEBUG ("Allocator commit returned %x", hres);
return S_OK;
}
@ -199,57 +203,126 @@ STDMETHODIMP VideoFakeSrcPin::CopyToDestinationBuffer (byte *srcbuf, byte *dstbu
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;
/* TODO: Use more of the arguments here? */
HRESULT hres = GetDeliveryBuffer(&pSample, NULL, NULL, 0);
if (SUCCEEDED (hres))
StartUsingOutputPin();
while (attempts < MAX_ATTEMPTS)
{
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();
hres = GetDeliveryBuffer(&pSample, NULL, NULL, 0);
if (SUCCEEDED (hres))
return GST_FLOW_OK;
else if (hres == VFW_E_NOT_CONNECTED)
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;
}
else {
GST_WARNING ("Could not get sample for delivery to sink: %x", hres);
return GST_FLOW_ERROR;
}
}
STDMETHODIMP VideoFakeSrcPin::Flush ()
@ -259,7 +332,8 @@ STDMETHODIMP VideoFakeSrcPin::Flush ()
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;
m_pOutputPin = new VideoFakeSrcPin ((CSource *)this, &m_critsec, &hr);
@ -279,3 +353,62 @@ 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;
}

View file

@ -22,7 +22,7 @@
#include <streams.h>
#include <gst/gst.h>
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__ */

View file

@ -28,13 +28,15 @@
#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 <songbird@songbirdnest.com>");
GST_DEBUG_CATEGORY_STATIC (dshowvideosink_debug);
GST_DEBUG_CATEGORY (dshowvideosink_debug);
#define GST_CAT_DEFAULT dshowvideosink_debug
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->fakesrc = NULL;
sink->filter_graph = NULL;
sink->filter_media_event = NULL;
sink->keep_aspect_ratio = FALSE;
sink->full_screen = FALSE;
@ -396,6 +399,21 @@ gst_dshow_get_pin_from_filter (IBaseFilter *filter, PIN_DIRECTION pindir, IPin *
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 */
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");
switch (message) {
case WM_GRAPH_NOTIFY:
gst_dshowvideosink_handle_event (sink);
return 0;
case WM_PAINT:
sink->renderersupport->PaintWindow ();
break;
@ -436,9 +457,13 @@ WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM 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) {
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;
@ -538,6 +563,8 @@ gst_dshowvideosink_window_thread (GstDshowVideoSink * sink)
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);
@ -637,6 +664,8 @@ static void gst_dshowvideosink_set_window_for_renderer (GstDshowVideoSink *sink)
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));
@ -650,6 +679,23 @@ gst_dshowvideosink_prepare_window (GstDshowVideoSink *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
@ -1152,6 +1198,11 @@ 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:
*
@ -1203,6 +1254,8 @@ gst_dshowvideosink_build_filtergraph (GstDshowVideoSink *sink)
goto error;
}
if (comInit)
CoUninitialize();
return TRUE;
error:
@ -1216,6 +1269,14 @@ error:
sink->filter_graph = NULL;
}
if (sink->filter_media_event) {
sink->filter_media_event->Release();
sink->filter_media_event = NULL;
}
if (comInit)
CoUninitialize();
return FALSE;
}
@ -1234,18 +1295,24 @@ 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 (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;
}
@ -1296,7 +1363,7 @@ gst_dshowvideosink_render (GstBaseSink *bsink, GstBuffer *buffer)
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");
GST_DEBUG_OBJECT (sink, "Done pushing buffer through fakesrc->renderer: %s", gst_flow_get_name(ret));
return ret;
}

View file

@ -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;