mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-23 08:46:40 +00:00
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:
parent
e3fcf51e2c
commit
2e401cc71d
4 changed files with 2045 additions and 1833 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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__ */
|
||||
|
|
|
@ -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, ¶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)
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in a new issue