//==========================================================================; // // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY // KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR // PURPOSE. // // Copyright (c) 1992 - 1998 Microsoft Corporation. All Rights Reserved. // //--------------------------------------------------------------------------; #include #include "vidprop.h" #include #include "sampvid.h" #if (1100 > _MSC_VER) #include #else #include #endif // // // What this sample illustrates // // A simple video renderer that draws video into a text shaped window on // Windows NT and a simple popup window on Windows 95. Shows to how use // the base video renderer classes from the ActiveMovie SDK. Implements // a property page to allow users to find out quality management details. // // // Summary // // This is a sample ActiveMovie video renderer - the filter is based on the // CBaseVideoRenderer base class. The base class handles all the seeking, // synchronisation and quality management necessary for video renderers. In // particular we override the DoRenderSample and PrepareRender methods so // that we can draw images and realize palettes as necessary in Windows. // // // Implementation // // The original idea was that the renderer would create a custom window that // spelt out ActiveX, in the letters the video would be displayed. To create // a window with a non rectangular clip region like this meant using paths. // Unfortunately these are only truly supported on WindowsNT so for Win95 we // create a simple popup window (ie no system menu nor accellerator boxes). // // The renderer supports both IBasicVideo and IVideoWindow that is achieved // fairly simply by inheriting our renderer from the CBaseControlVideo and // CBaseControlWindow base classes. To fully implement these interfaces we // must then override and implement some more PURE virtual methods such as // GetVideoFormat and Get/SetSourceRect (which all live in VIDEOTXT.CPP). // // Because we are either a simple popup window or a text shaped window we may // not have a title bar for the user to grab to move the window around. So we // handle WM_NCHITTEST messages (with HTCLIENT) to effectively enable window // dragging by clicking on the video client area. // // We make heavy use of other base classes, notably the CImageAllocator which // provides buffers that are really DIBSECTIONs. This enables faster drawing // of video (and is the same trick pulled by the real runtime renderer). We // also use CImageDisplay to match up optimal drawing formats and for video // type checking, CImagePalette for general palette creation and handling and // last but not least CDrawImage that can be used for general video drawing. // // // Demonstration instructions // // Start GRAPHEDT available in the ActiveMovie SDK tools. Drag and drop any // MPEG, AVI or MOV file into the tool and it will be rendered. Then go to // the filters in the graph and find the filter (box) titled "Video Renderer" // This is the filter we will be replacing with the sample video renderer. // Then click on the box and hit DELETE. After that go to the Graph menu and // select "Insert Filters", from the dialog box find and select the "Sample // Renderer" and then dismiss the dialog. Back in the graph layout find the // output pin of the filter that was connected to the input of the video // renderer you just deleted, right click and select "Render". You should // see it being connected to the input pin of the renderer you just inserted // // Click Pause and Run on the GRAPHEDT frame and you will see the video... // // // Files // // sampvid.cpp Main implementation of the video renderer // sampvid.def What APIs the DLL will import and export // sampvid.h Class definition of the derived renderer // sampvid.rc Dialog box template for our property page // videotxt.cpp The code to look after a video window // vidprop.cpp The implementation of the property page // vidprop.h The class definition for the property page // makefile How to build it... // // // Base classes used // // CImageAllocator A DIBSECTION video image allocator // CVideoInputPin IPin and IMemInputPin interfaces // CImageDisplay Manages the video display type // CMediaType Source connection media type // CVideoText Does the actual video rendering // CImagePalette Looks after managing a palette // CDrawImage Does the actual image drawing // // // Setup data const AMOVIESETUP_MEDIATYPE sudPinTypes = { &MEDIATYPE_Video, // Major type &MEDIASUBTYPE_NULL // Minor type }; const AMOVIESETUP_PIN sudPins = { L"Input", // Name of the pin FALSE, // Is pin rendered FALSE, // Is an output pin FALSE, // Ok for no pins FALSE, // Allowed many &CLSID_NULL, // Connects to filter L"Output", // Connects to pin 1, // Number of pin types &sudPinTypes // Details for pins }; const AMOVIESETUP_FILTER sudSampVid = { &CLSID_SampleRenderer, // Filter CLSID L"Sample Video Renderer", // Filter name MERIT_DO_NOT_USE, // Filter merit 1, // Number pins &sudPins // Pin details }; // List of class IDs and creator functions for the class factory. This // provides the link between the OLE entry point in the DLL and an object // being created. The class factory will call the static CreateInstance CFactoryTemplate g_Templates[] = { { L"Sample Video Renderer" , &CLSID_SampleRenderer , CVideoRenderer::CreateInstance , NULL , &sudSampVid } , { L"Quality Property Page" , &CLSID_SampleQuality , CQualityProperties::CreateInstance } }; int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]); // // CreateInstance // // This goes in the factory template table to create new filter instances // CUnknown * WINAPI CVideoRenderer::CreateInstance(LPUNKNOWN pUnk, HRESULT *phr) { return new CVideoRenderer(NAME("Sample Video Renderer"),pUnk,phr); } // CreateInstance #pragma warning(disable:4355) // // Constructor // CVideoRenderer::CVideoRenderer(TCHAR *pName, LPUNKNOWN pUnk, HRESULT *phr) : CBaseVideoRenderer(CLSID_SampleRenderer,pName,pUnk,phr), m_InputPin(NAME("Video Pin"),this,&m_InterfaceLock,phr,L"Input"), m_ImageAllocator(this,NAME("Sample video allocator"),phr), m_VideoText(NAME("Video properties"),GetOwner(),phr,&m_InterfaceLock,this), m_ImagePalette(this,&m_VideoText,&m_DrawImage), m_DrawImage(&m_VideoText) { // Store the video input pin m_pInputPin = &m_InputPin; // Reset the current video size m_VideoSize.cx = 0; m_VideoSize.cy = 0; // Initialise the window and control interfaces m_VideoText.SetControlVideoPin(&m_InputPin); m_VideoText.SetControlWindowPin(&m_InputPin); } // (Constructor) // // Destructor // CVideoRenderer::~CVideoRenderer() { m_pInputPin = NULL; } // (Destructor) // // CheckMediaType // // Check the proposed video media type // HRESULT CVideoRenderer::CheckMediaType(const CMediaType *pmtIn) { return m_Display.CheckMediaType(pmtIn); } // CheckMediaType // // GetPin // // We only support one input pin and it is numbered zero // CBasePin *CVideoRenderer::GetPin(int n) { ASSERT(n == 0); if (n != 0) { return NULL; } // Assign the input pin if not already done so if (m_pInputPin == NULL) { m_pInputPin = &m_InputPin; } return m_pInputPin; } // GetPin // // NonDelegatingQueryInterface // // Overriden to say what interfaces we support and where // STDMETHODIMP CVideoRenderer::NonDelegatingQueryInterface(REFIID riid,void **ppv) { CheckPointer(ppv,E_POINTER); if (riid == IID_ISpecifyPropertyPages) { return GetInterface((ISpecifyPropertyPages *)this, ppv); } else if (riid == IID_IVideoWindow) { return m_VideoText.NonDelegatingQueryInterface(riid,ppv); } else if (riid == IID_IBasicVideo) { return m_VideoText.NonDelegatingQueryInterface(riid,ppv); } return CBaseVideoRenderer::NonDelegatingQueryInterface(riid,ppv); } // NonDelegatingQueryInterface // // GetPages // // Return the CLSIDs for the property pages we support // STDMETHODIMP CVideoRenderer::GetPages(CAUUID *pPages) { CheckPointer(pPages,E_POINTER); pPages->cElems = 1; pPages->pElems = (GUID *) CoTaskMemAlloc(sizeof(GUID)); if (pPages->pElems == NULL) { return E_OUTOFMEMORY; } pPages->pElems[0] = CLSID_SampleQuality; return NOERROR; } // GetPages // // DoRenderSample // // Have the drawing object render the current image // HRESULT CVideoRenderer::DoRenderSample(IMediaSample *pMediaSample) { return m_DrawImage.DrawImage(pMediaSample); } // DoRenderSample // // PrepareRender // // Overriden to realise the palette before drawing. We used to have to realise // the palette on every frame because we could never be sure of receiving top // level messages like WM_PALETTECHANGED. However the plug in distributor will // now make sure we get these but we still have to do this because otherwise // we may not find the palette being realised before the thread comes to draw void CVideoRenderer::PrepareRender() { // Realise the palette on this thread m_VideoText.DoRealisePalette(); } // PrepareRender // // Active // // The auto show flag is used to have the window shown automatically when we // change state. We do this only when moving to paused or running, when there // is no outstanding EC_USERABORT set and when the window is not already up // This can be changed through the IVideoWindow interface AutoShow property. // If the window is not currently visible then we are showing it because of // a state change to paused or running, in which case there is no point in // the video window sending an EC_REPAINT as we're getting an image anyway // HRESULT CVideoRenderer::Active() { HWND hwnd = m_VideoText.GetWindowHWND(); NOTE("AutoShowWindow"); if (m_VideoText.IsAutoShowEnabled() == TRUE) { if (m_bAbort == FALSE) { if (IsWindowVisible(hwnd) == FALSE) { NOTE("Executing AutoShowWindow"); SetRepaintStatus(FALSE); m_VideoText.PerformanceAlignWindow(); m_VideoText.DoShowWindow(SW_SHOWNORMAL); m_VideoText.DoSetWindowForeground(TRUE); } } } return CBaseVideoRenderer::Active(); } // Active // // SetMediaType // // We store a copy of the media type used for the connection in the renderer // because it is required by many different parts of the running renderer // This can be called when we come to draw a media sample that has a format // change with it. We normally delay type changes until they are really due // for rendering otherwise we will change types too early if the source has // allocated a queue of samples. In our case this isn't a problem because we // only ever receive one sample at a time so it's safe to change immediately // HRESULT CVideoRenderer::SetMediaType(const CMediaType *pmt) { CAutoLock cInterfaceLock(&m_InterfaceLock); CMediaType StoreFormat(m_mtIn); HRESULT hr = NOERROR; // Fill out the optional fields in the VIDEOINFOHEADER m_mtIn = *pmt; VIDEOINFO *pVideoInfo = (VIDEOINFO *) m_mtIn.Format(); m_Display.UpdateFormat(pVideoInfo); // We set the new palette before completing so that the method can look // at the old RGB colours we used and compare them with the new set, if // they're all identical colours we don't need to change the palette hr = m_ImagePalette.PreparePalette(&m_mtIn,&StoreFormat,NULL); if (FAILED(hr)) { return hr; } // Complete the initialisation m_DrawImage.NotifyMediaType(&m_mtIn); m_ImageAllocator.NotifyMediaType(&m_mtIn); return NOERROR; } // SetMediaType // // BreakConnect // // This is called when a connection or an attempted connection is terminated // and lets us to reset the connection flag held by the base class renderer // The filter object may be hanging onto an image to use for refreshing the // video window so that must be freed (the allocator decommit may be waiting // for that image to return before completing) then we must also uninstall // any palette we were using, reset anything set with the control interfaces // then set our overall state back to disconnected ready for the next time HRESULT CVideoRenderer::BreakConnect() { CAutoLock cInterfaceLock(&m_InterfaceLock); // Check we are in a valid state HRESULT hr = CBaseVideoRenderer::BreakConnect(); if (FAILED(hr)) { return hr; } // The window is not used when disconnected IPin *pPin = m_InputPin.GetConnected(); if (pPin) SendNotifyWindow(pPin,NULL); // The base class break connect disables us from sending any EC_REPAINT // events which is important otherwise when we come down here to remove // our palette we end up painting the window again - which in turn sees // there is no image to draw and would otherwise send a redundant event m_ImagePalette.RemovePalette(); m_mtIn.ResetFormatBuffer(); return NOERROR; } // BreakConnect // // CompleteConnect // // When we complete connection we need to see if the video has changed sizes // If it has then we activate the window and reset the source and destination // rectangles. If the video is the same size then we bomb out early. By doing // this we make sure that temporary disconnections such as when we go into a // fullscreen mode do not cause unnecessary property changes. The basic ethos // is that all properties should be persistent across connections if possible // HRESULT CVideoRenderer::CompleteConnect(IPin *pReceivePin) { CAutoLock cInterfaceLock(&m_InterfaceLock); CBaseVideoRenderer::CompleteConnect(pReceivePin); m_DrawImage.ResetPaletteVersion(); // Has the video size changed between connections VIDEOINFOHEADER *pVideoInfo = (VIDEOINFOHEADER *) m_mtIn.Format(); if (pVideoInfo->bmiHeader.biWidth == m_VideoSize.cx) { if (pVideoInfo->bmiHeader.biHeight == m_VideoSize.cy) { return NOERROR; } } // Pass the video window handle upstream HWND hwnd = m_VideoText.GetWindowHWND(); NOTE1("Sending EC_NOTIFY_WINDOW %x",hwnd); SendNotifyWindow(pReceivePin,hwnd); // Set them for the current video dimensions m_DrawImage.SetDrawContext(); m_VideoSize.cx = pVideoInfo->bmiHeader.biWidth; m_VideoSize.cy = pVideoInfo->bmiHeader.biHeight; m_VideoText.SetDefaultSourceRect(); m_VideoText.SetDefaultTargetRect(); m_VideoText.OnVideoSizeChange(); m_VideoText.ActivateWindow(); return NOERROR; } // CompleteConnect // // OnReceiveFirstSample // // Use the image just delivered to display a poster frame // void CVideoRenderer::OnReceiveFirstSample(IMediaSample *pMediaSample) { DoRenderSample(pMediaSample); } // OnReceiveFirstSample // Constructor CVideoInputPin::CVideoInputPin(TCHAR *pObjectName, CVideoRenderer *pRenderer, CCritSec *pInterfaceLock, HRESULT *phr, LPCWSTR pPinName) : CRendererInputPin(pRenderer,phr,pPinName), m_pRenderer(pRenderer), m_pInterfaceLock(pInterfaceLock) { ASSERT(m_pRenderer); ASSERT(pInterfaceLock); } // (Constructor) // // GetAllocator // // This overrides the CBaseInputPin virtual method to return our allocator // we create to pass shared memory DIB buffers that GDI can directly access // When NotifyAllocator is called it sets the current allocator in the base // input pin class (m_pAllocator), this is what GetAllocator should return // unless it is NULL in which case we return the allocator we would like // STDMETHODIMP CVideoInputPin::GetAllocator(IMemAllocator **ppAllocator) { CAutoLock cInterfaceLock(m_pInterfaceLock); CheckPointer(ppAllocator,E_POINTER); // Has an allocator been set yet in the base class if (m_pAllocator == NULL) { m_pAllocator = &m_pRenderer->m_ImageAllocator; m_pAllocator->AddRef(); } m_pAllocator->AddRef(); *ppAllocator = m_pAllocator; return NOERROR; } // GetAllocator // // NotifyAllocator // // The COM specification says any two IUnknown pointers to the same object // should always match which provides a way for us to see if they are using // our DIB allocator or not. Since we are only really interested in equality // and our object always hands out the same IMemAllocator interface we can // just see if the pointers match. If they are we set a flag in the main // renderer as the window needs to know whether it can do fast rendering // STDMETHODIMP CVideoInputPin::NotifyAllocator(IMemAllocator *pAllocator,BOOL bReadOnly) { CAutoLock cInterfaceLock(m_pInterfaceLock); // Make sure the base class gets a look HRESULT hr = CBaseInputPin::NotifyAllocator(pAllocator,bReadOnly); if (FAILED(hr)) { return hr; } // Whose allocator is the source going to use m_pRenderer->m_DrawImage.NotifyAllocator(FALSE); if (pAllocator == &m_pRenderer->m_ImageAllocator) { m_pRenderer->m_DrawImage.NotifyAllocator(TRUE); } return NOERROR; } // NotifyAllocator // // DllRegisterSever // // Handle the registration of this filter // STDAPI DllRegisterServer() { return AMovieDllRegisterServer2( TRUE ); } // DllRegisterServer // // DllUnregisterServer // STDAPI DllUnregisterServer() { return AMovieDllRegisterServer2( FALSE ); } // DllUnregisterServer