/* * $Id$ * * (C) 2003-2006 Gabest * (C) 2006-2010 see AUTHORS * * This file is part of mplayerc. * * Mplayerc is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * Mplayerc is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "stdafx.h" #include "mplayerc.h" #include "MainFrm.h" #include #include #include #include #include "RealMediaGraph.h" #include "RealMediaWindowlessSite.h" #include #include #include #include "../../DSUtil/DSUtil.h" #include "AuthDlg.h" using namespace DSObjects; // CRealMediaPlayer CRealMediaPlayer::CRealMediaPlayer(HWND hWndParent, CRealMediaGraph* pRMG) : CUnknown(NAME("CRealMediaPlayer"), NULL) , m_pRMG(pRMG) , m_hWndParent(hWndParent) , m_fpCreateEngine(NULL), m_fpCloseEngine(NULL), m_hRealMediaCore(NULL) , m_State(State_Stopped), m_UserState(State_Stopped), m_nCurrent(0), m_nDuration(0) , m_VideoSize(0, 0) , m_fVideoSizeChanged(true) { } CRealMediaPlayer::~CRealMediaPlayer() { Deinit(); } bool CRealMediaPlayer::Init() { CString prefs(_T("Software\\RealNetworks\\Preferences")); CRegKey key; if(ERROR_SUCCESS != key.Open(HKEY_CLASSES_ROOT, prefs + _T("\\DT_Common"), KEY_READ)) return(false); TCHAR buff[_MAX_PATH]; ULONG len = sizeof(buff)/sizeof(buff[0]); if(ERROR_SUCCESS != key.QueryStringValue(NULL, buff, &len)) return(false); key.Close(); m_hRealMediaCore = LoadLibrary(CString(buff) + _T("pnen3260.dll")); if(!m_hRealMediaCore) return(false); m_fpCreateEngine = (FPRMCREATEENGINE)GetProcAddress(m_hRealMediaCore, "CreateEngine"); m_fpCloseEngine = (FPRMCLOSEENGINE)GetProcAddress(m_hRealMediaCore, "CloseEngine"); m_fpSetDLLAccessPath = (FPRMSETDLLACCESSPATH)GetProcAddress(m_hRealMediaCore, "SetDLLAccessPath"); if(!m_fpCreateEngine || !m_fpCloseEngine || !m_fpSetDLLAccessPath) return(false); if(ERROR_SUCCESS == key.Open(HKEY_CLASSES_ROOT, prefs, KEY_READ)) { CString dllpaths; len = sizeof(buff)/sizeof(buff[0]); for(int i = 0; ERROR_SUCCESS == key.EnumKey(i, buff, &len); i++,len = sizeof(buff)/sizeof(buff[0])) { CRegKey key2; TCHAR buff2[_MAX_PATH]; ULONG len2 = sizeof(buff2)/sizeof(buff2[0]); if(ERROR_SUCCESS != key2.Open(HKEY_CLASSES_ROOT, prefs + _T("\\") + buff, KEY_READ) || ERROR_SUCCESS != key2.QueryStringValue(NULL, buff2, &len2)) continue; dllpaths += CString(buff) + '=' + buff2 + '|'; } key.Close(); if(!dllpaths.IsEmpty()) { char* s = DNew char[dllpaths.GetLength()+1]; strcpy(s, CStringA(dllpaths)); for(int i = 0, j = strlen(s); i < j; i++) { if(s[i] == '|') s[i] = '\0'; } m_fpSetDLLAccessPath(s); delete [] s; } } if(PNR_OK != m_fpCreateEngine(&m_pEngine)) return(false); if(PNR_OK != m_pEngine->CreatePlayer(*&m_pPlayer)) return(false); if(!(m_pSiteManager = m_pPlayer) || !(m_pCommonClassFactory = m_pPlayer)) return(false); m_pAudioPlayer = m_pPlayer; m_pAudioPlayer->AddPostMixHook(static_cast(this), FALSE, FALSE); // m_pVolume = m_pAudioPlayer->GetDeviceVolume(); m_pVolume = m_pAudioPlayer->GetAudioVolume(); // IRMAVolume::SetVolume has a huge latency when used via GetAudioVolume, // but by lowering this audio pushdown thing it can get better CComQIPtr pAP = m_pAudioPlayer; if(pAP) pAP->SetAudioPushdown(300); // 100ms makes the playback sound choppy, 200ms looks ok, but for safety we set this to 300ms... :P CComQIPtr pErrorSinkControl = m_pPlayer; if(pErrorSinkControl) pErrorSinkControl->AddErrorSink(static_cast(this), PNLOG_EMERG, PNLOG_INFO); if(PNR_OK != m_pPlayer->AddAdviseSink(static_cast(this))) return(false); if(PNR_OK != m_pPlayer->SetClientContext((IUnknown*)(INonDelegatingUnknown*)(this))) return(false); // TODO /* if(CComQIPtr pPrefs = m_pPlayer) { CComPtr pBuffer; HRESULT hr = pPrefs->ReadPref("HTTPProxyHost", *&pBuffer); UCHAR* pData = NULL; ULONG32 ulLength = 0; hr = pBuffer->Get(pData, ulLength); pBuffer = NULL; hr = m_pCommonClassFactory->CreateInstance(CLSID_IRMABuffer, (void**)&pBuffer); hr = pBuffer->SetSize(strlen("localhost")+1); pData = pBuffer->GetBuffer(); strcpy((char*)pData, "localhost"); hr = pBuffer->Set(pData, strlen("localhost")+1); pData = NULL; ulLength = 0; hr = pBuffer->Get(pData, ulLength); hr = pPrefs->WritePref("HTTPProxyHost", pBuffer); hr = hr; } */ return(true); } void CRealMediaPlayer::Deinit() { if(m_pPlayer) { m_pPlayer->Stop(); CComQIPtr pErrorSinkControl = m_pPlayer; if(pErrorSinkControl) pErrorSinkControl->RemoveErrorSink(static_cast(this)); m_pPlayer->RemoveAdviseSink(static_cast(this)); m_pVolume = NULL; m_pAudioPlayer->RemovePostMixHook(static_cast(this)); m_pAudioPlayer.Release(); m_pEngine->ClosePlayer(m_pPlayer); m_pSiteManager.Release(); m_pCommonClassFactory.Release(); m_pPlayer = NULL; } if(m_pEngine) { m_fpCloseEngine(m_pEngine); m_pEngine.Detach(); } if(m_hRealMediaCore) { FreeLibrary(m_hRealMediaCore); m_hRealMediaCore = NULL; } } STDMETHODIMP CRealMediaPlayer::NonDelegatingQueryInterface(REFIID riid, void** ppv) { CheckPointer(ppv, E_POINTER); return QI2(IRMAErrorSink) QI2(IRMAClientAdviseSink) QI2(IRMAAuthenticationManager) QI2(IRMASiteSupplier) QI2(IRMAPassiveSiteWatcher) QI2(IRMAAudioHook) __super::NonDelegatingQueryInterface(riid, ppv); } char* AllocateErrorMessage(const char* msg) { char* errmsg = NULL; int len = strlen(msg); if(len > 0) { errmsg = (char*)CoTaskMemAlloc(len+1); if (errmsg) strcpy(errmsg, msg); } return errmsg; } // IRMAErrorSink STDMETHODIMP CRealMediaPlayer::ErrorOccurred(const UINT8 unSeverity, const UINT32 ulRMACode, const UINT32 ulUserCode, const char* pUserString, const char* pMoreInfoURL) { char* errmsg = NULL; if(unSeverity < 5) { if(CComQIPtr pErrorMessages = m_pPlayer) { CComPtr pBuffer = pErrorMessages->GetErrorText(ulRMACode); if(pBuffer) { char* buff = (char*)pBuffer->GetBuffer(); errmsg = AllocateErrorMessage(buff); } } if(!errmsg) { errmsg = AllocateErrorMessage("RealMedia error"); TRACE("RealMedia error\n"); } m_pRMG->NotifyEvent(EC_BG_ERROR, (LONG_PTR)errmsg, 0); } return PNR_OK; } // IRMAClientAdviseSink STDMETHODIMP CRealMediaPlayer::OnPosLength(UINT32 ulPosition, UINT32 ulLength) { m_nCurrent = (REFERENCE_TIME)ulPosition*10000; m_nDuration = (REFERENCE_TIME)ulLength*10000; return PNR_OK; } STDMETHODIMP CRealMediaPlayer::OnPresentationOpened() { return PNR_OK; } STDMETHODIMP CRealMediaPlayer::OnPresentationClosed() { return PNR_OK; } STDMETHODIMP CRealMediaPlayer::OnStatisticsChanged() { m_pRMG->NotifyEvent(EC_LENGTH_CHANGED); return PNR_OK; } STDMETHODIMP CRealMediaPlayer::OnPreSeek(UINT32 ulOldTime, UINT32 ulNewTime) { m_nCurrent = (REFERENCE_TIME)ulNewTime*10000; return PNR_OK; } STDMETHODIMP CRealMediaPlayer::OnPostSeek(UINT32 ulOldTime, UINT32 ulNewTime) { m_nCurrent = (REFERENCE_TIME)ulNewTime*10000; return PNR_OK; } STDMETHODIMP CRealMediaPlayer::OnStop() { m_nCurrent = 0; m_State = State_Stopped; if(m_UserState != State_Stopped) m_pRMG->NotifyEvent(EC_COMPLETE); return PNR_OK; } STDMETHODIMP CRealMediaPlayer::OnPause(UINT32 ulTime) { m_State = State_Paused; return PNR_OK; } STDMETHODIMP CRealMediaPlayer::OnBegin(UINT32 ulTime) { m_State = State_Running; return PNR_OK; } STDMETHODIMP CRealMediaPlayer::OnBuffering(UINT32 ulFlags, UINT16 unPercentComplete) { m_unPercentComplete = unPercentComplete; return PNR_OK; } STDMETHODIMP CRealMediaPlayer::OnContacting(const char* pHostName) { return PNR_OK; } // IRMAAuthenticationManager STDMETHODIMP CRealMediaPlayer::HandleAuthenticationRequest(IRMAAuthenticationManagerResponse* pResponse) { CAuthDlg dlg; if(dlg.DoModal() == IDOK) { pResponse->AuthenticationRequestDone( PNR_OK, CStringA(dlg.m_username), CStringA(dlg.m_password)); return PNR_OK; } return pResponse->AuthenticationRequestDone(PNR_NOT_AUTHORIZED, NULL, NULL); } // IRMASiteSupplier STDMETHODIMP CRealMediaPlayer::SitesNeeded(UINT32 uRequestID, IRMAValues* pProps) { if(!pProps) return PNR_INVALID_PARAMETER; if(m_pTheSite || m_pTheSite2 || !m_hWndParent) return PNR_UNEXPECTED; HRESULT hr = PNR_OK; if(!CreateSite(&m_pTheSite)) return E_FAIL; ULONG refc = ((IRMASite*)m_pTheSite)->AddRef(); refc = ((IRMASite*)m_pTheSite)->Release(); if(!(m_pTheSite2 = m_pTheSite)) return E_NOINTERFACE; CComQIPtr pSiteProps = m_pTheSite; if(!pSiteProps) return E_NOINTERFACE; IRMABuffer* pValue; // no idea what these supposed to do... but they were in the example hr = pProps->GetPropertyCString("playto", pValue); if(PNR_OK == hr) { pSiteProps->SetPropertyCString("channel", pValue); pValue->Release(); } else { hr = pProps->GetPropertyCString("name", pValue); if(PNR_OK == hr) { pSiteProps->SetPropertyCString("LayoutGroup", pValue); pValue->Release(); } } m_pTheSite2->AddPassiveSiteWatcher(static_cast(this)); hr = m_pSiteManager->AddSite(m_pTheSite); if(PNR_OK != hr) return hr; m_CreatedSites[uRequestID] = m_pTheSite; return hr; } STDMETHODIMP CRealMediaPlayer::SitesNotNeeded(UINT32 uRequestID) { IRMASite* pSite; if(!m_CreatedSites.Lookup(uRequestID, pSite)) return PNR_INVALID_PARAMETER; m_CreatedSites.RemoveKey(uRequestID); m_pSiteManager->RemoveSite(pSite); m_pTheSite2->RemovePassiveSiteWatcher(static_cast(this)); m_pTheSite.Release(); m_pTheSite2.Release(); DestroySite(pSite); return PNR_OK; } STDMETHODIMP CRealMediaPlayer::BeginChangeLayout() { return E_NOTIMPL; } STDMETHODIMP CRealMediaPlayer::DoneChangeLayout() { if(m_fVideoSizeChanged) { m_pRMG->NotifyEvent(EC_VIDEO_SIZE_CHANGED, MAKELPARAM(m_VideoSize.cx, m_VideoSize.cy), 0); m_fVideoSizeChanged = false; } return PNR_OK; } // IRMAPassiveSiteWatcher STDMETHODIMP CRealMediaPlayer::PositionChanged(PNxPoint* pos) { return E_NOTIMPL; } STDMETHODIMP CRealMediaPlayer::SizeChanged(PNxSize* size) { if(m_VideoSize.cx == 0 || m_VideoSize.cy == 0) { m_fVideoSizeChanged = true; m_VideoSize.cx = size->cx; m_VideoSize.cy = size->cy; } return PNR_OK; } // IRMAAudioHook STDMETHODIMP CRealMediaPlayer::OnBuffer(RMAAudioData* pAudioInData, RMAAudioData* pAudioOutData) { return E_NOTIMPL; } STDMETHODIMP CRealMediaPlayer::OnInit(RMAAudioFormat* pFormat) { m_pRMG->NotifyEvent(EC_BG_AUDIO_CHANGED, pFormat->uChannels, 0); return PNR_OK; } // // CRealMediaPlayerWindowed // CRealMediaPlayerWindowed::CRealMediaPlayerWindowed(HWND hWndParent, CRealMediaGraph* pRMG) : CRealMediaPlayer(hWndParent, pRMG) { if(!m_wndWindowFrame.CreateEx(WS_EX_NOPARENTNOTIFY, NULL, NULL, WS_CHILD|WS_VISIBLE|WS_CLIPSIBLINGS|WS_CLIPCHILDREN|WS_VISIBLE, CRect(0, 0, 0, 0), CWnd::FromHandle(m_hWndParent), 0, NULL)) return; if(!m_wndDestFrame.Create(NULL, NULL, WS_CHILD|WS_VISIBLE|WS_CLIPSIBLINGS|WS_CLIPCHILDREN, CRect(0, 0, 0, 0), &m_wndWindowFrame, 0, NULL)) return; } CRealMediaPlayerWindowed::~CRealMediaPlayerWindowed() { m_wndDestFrame.DestroyWindow(); m_wndWindowFrame.DestroyWindow(); } void CRealMediaPlayerWindowed::SetWindowRect(CRect r) { if(IsWindow(m_wndWindowFrame.m_hWnd)) m_wndWindowFrame.MoveWindow(r); } void CRealMediaPlayerWindowed::SetDestRect(CRect r) { if(IsWindow(m_wndDestFrame.m_hWnd)) m_wndDestFrame.MoveWindow(r); if(m_pTheSite) { PNxSize s = {r.Width(), r.Height()}; m_pTheSite->SetSize(s); } } bool CRealMediaPlayerWindowed::CreateSite(IRMASite** ppSite) { if(!ppSite) return(false); CComPtr pSiteWindowed; if(PNR_OK != m_pCommonClassFactory->CreateInstance(CLSID_IRMASiteWindowed, (void**)&pSiteWindowed)) return(false); DWORD style = WS_CHILD|WS_VISIBLE|WS_CLIPSIBLINGS|WS_CLIPCHILDREN; if(!AfxGetAppSettings().fIntRealMedia) style |= WS_DISABLED; if(PNR_OK != pSiteWindowed->Create(m_wndDestFrame.m_hWnd, style)) return(false); *ppSite = CComQIPtr(pSiteWindowed).Detach(); return !!(*ppSite); } void CRealMediaPlayerWindowed::DestroySite(IRMASite* pSite) { if(CComQIPtr pRMASiteWindowed = pSite) pRMASiteWindowed->Destroy(); } // // CRealMediaPlayerWindowless // CRealMediaPlayerWindowless::CRealMediaPlayerWindowless(HWND hWndParent, CRealMediaGraph* pRMG) : CRealMediaPlayer(hWndParent, pRMG) { AppSettings& s = AfxGetAppSettings(); bool bFullscreen = (AfxGetApp()->m_pMainWnd != NULL) && (((CMainFrame*)AfxGetApp()->m_pMainWnd)->IsD3DFullScreenMode()); switch(s.iRMVideoRendererType) { default: case VIDRNDT_RM_DX7: if(FAILED(CreateAP7(CLSID_RM7AllocatorPresenter, hWndParent, &m_pRMAP))) return; break; case VIDRNDT_RM_DX9: if(FAILED(CreateAP9(CLSID_RM9AllocatorPresenter, hWndParent, bFullscreen, &m_pRMAP))) return; break; } } CRealMediaPlayerWindowless::~CRealMediaPlayerWindowless() { } STDMETHODIMP CRealMediaPlayerWindowless::NonDelegatingQueryInterface(REFIID riid, void** ppv) { CheckPointer(ppv, E_POINTER); return (m_pRMAP && (riid == __uuidof(ISubPicAllocatorPresenter) || riid == IID_IRMAVideoSurface)) ? m_pRMAP->QueryInterface(riid, ppv) : __super::NonDelegatingQueryInterface(riid, ppv); } bool CRealMediaPlayerWindowless::CreateSite(IRMASite** ppSite) { if(!ppSite || !m_pRMAP) return(false); HRESULT hr = S_OK; CRealMediaWindowlessSite* pWMWlS; CComPtr pSiteWindowless; pSiteWindowless = (IRMASiteWindowless*)(pWMWlS = DNew CRealMediaWindowlessSite(hr, m_pPlayer, NULL, NULL)); if(FAILED(hr)) return(false); pWMWlS->SetBltService(CComQIPtr(m_pRMAP)); *ppSite = CComQIPtr(pSiteWindowless).Detach(); return !!(*ppSite); } void CRealMediaPlayerWindowless::DestroySite(IRMASite* pSite) { } STDMETHODIMP CRealMediaPlayerWindowless::SizeChanged(PNxSize* size) { if(CComQIPtr pRMAVS = m_pRMAP) { RMABitmapInfoHeader BitmapInfo; memset(&BitmapInfo, 0, sizeof(BitmapInfo)); BitmapInfo.biWidth = size->cx; BitmapInfo.biHeight = size->cy; pRMAVS->BeginOptimizedBlt(&BitmapInfo); } return __super::SizeChanged(size); } //////////////// CRealMediaGraph::CRealMediaGraph(HWND hWndParent, HRESULT& hr) : CBaseGraph() { hr = S_OK; m_pRMP = AfxGetAppSettings().iRMVideoRendererType == VIDRNDT_RM_DEFAULT ? (CRealMediaPlayer*)DNew CRealMediaPlayerWindowed(hWndParent, this) : (CRealMediaPlayer*)DNew CRealMediaPlayerWindowless(hWndParent, this); if(!m_pRMP) { hr = E_OUTOFMEMORY; return; } if(!m_pRMP->Init()) { delete m_pRMP, m_pRMP = NULL; hr = E_FAIL; return; } m_pRMP->AddRef(); } CRealMediaGraph::~CRealMediaGraph() { if(m_pRMP) { m_pRMP->Deinit(); m_pRMP->Release(); m_pRMP = NULL; } } STDMETHODIMP CRealMediaGraph::NonDelegatingQueryInterface(REFIID riid, void** ppv) { CheckPointer(ppv, E_POINTER); return (m_pRMP && (riid == __uuidof(ISubPicAllocatorPresenter) || riid == __uuidof(ISubPicAllocatorPresenter))) ? m_pRMP->QueryInterface(riid, ppv) : __super::NonDelegatingQueryInterface(riid, ppv); } // IGraphBuilder STDMETHODIMP CRealMediaGraph::RenderFile(LPCWSTR lpcwstrFile, LPCWSTR lpcwstrPlayList) { m_fn = lpcwstrFile; CHAR buff[_MAX_PATH] = {0}; WideCharToMultiByte(GetACP(), 0, lpcwstrFile, -1, buff, _MAX_PATH, 0, 0); CStringA fn(buff); if(fn.Find("://") < 0) fn = "file://" + fn; m_pRMP->m_unPercentComplete = 100; ClearMessageQueue(); if(PNR_OK != m_pRMP->m_pPlayer->OpenURL(fn)) return E_FAIL; m_pRMP->m_pPlayer->Pause()/*Stop()*/; // please, don't start just yet return S_OK; } // IMediaControl STDMETHODIMP CRealMediaGraph::Run() { if(m_pRMP->m_pPlayer->IsDone()) RenderFile(m_fn, NULL); m_pRMP->m_UserState = State_Running; return (PNR_OK == m_pRMP->m_pPlayer->Begin()) ? S_OK : E_FAIL; } STDMETHODIMP CRealMediaGraph::Pause() { m_pRMP->m_UserState = State_Paused; return (PNR_OK == m_pRMP->m_pPlayer->Pause()) ? S_OK : E_FAIL; } STDMETHODIMP CRealMediaGraph::Stop() { m_pRMP->m_UserState = State_Stopped; return (PNR_OK == m_pRMP->m_pPlayer->Stop()) ? S_OK : E_FAIL; } STDMETHODIMP CRealMediaGraph::GetState(LONG msTimeout, OAFilterState* pfs) { return pfs ? *pfs = m_pRMP->m_State, S_OK : E_POINTER; } // IMediaSeeking STDMETHODIMP CRealMediaGraph::GetDuration(LONGLONG* pDuration) { return pDuration ? *pDuration = m_pRMP->m_nDuration, S_OK : E_POINTER; } STDMETHODIMP CRealMediaGraph::GetCurrentPosition(LONGLONG* pCurrent) { return pCurrent ? *pCurrent = m_pRMP->m_nCurrent, S_OK : E_POINTER; } STDMETHODIMP CRealMediaGraph::SetPositions(LONGLONG* pCurrent, DWORD dwCurrentFlags, LONGLONG* pStop, DWORD dwStopFlags) { return (dwCurrentFlags&AM_SEEKING_AbsolutePositioning) && (PNR_OK == m_pRMP->m_pPlayer->Seek((ULONG)(*pCurrent / 10000))) ? S_OK : E_FAIL; } // IVideoWindow STDMETHODIMP CRealMediaGraph::SetWindowPosition(long Left, long Top, long Width, long Height) { if(m_pRMP) m_pRMP->SetWindowRect(CRect(CPoint(Left, Top), CSize(Width, Height))); return S_OK; } // IBasicVideo STDMETHODIMP CRealMediaGraph::SetDestinationPosition(long Left, long Top, long Width, long Height)// {return E_NOTIMPL;} { m_pRMP->SetDestRect(CRect(CPoint(Left, Top), CSize(Width, Height))); return S_OK; } STDMETHODIMP CRealMediaGraph::GetVideoSize(long* pWidth, long* pHeight) { if(!pWidth || !pHeight) return E_POINTER; *pWidth = m_pRMP->GetVideoSize().cx; *pHeight = m_pRMP->GetVideoSize().cy; return S_OK; } // IBasicAudio STDMETHODIMP CRealMediaGraph::put_Volume(long lVolume) { if(!m_pRMP->m_pVolume) return E_UNEXPECTED; UINT16 volume = (lVolume == -10000) ? 0 : (int)pow(10.0, ((double)lVolume)/5000+2); volume = max(min(volume, 100), 0); return PNR_OK == m_pRMP->m_pVolume->SetVolume(volume) ? S_OK : E_FAIL; } STDMETHODIMP CRealMediaGraph::get_Volume(long* plVolume) { if(!m_pRMP->m_pVolume) return E_UNEXPECTED; CheckPointer(plVolume, E_POINTER); UINT16 volume = m_pRMP->m_pVolume->GetVolume(); volume = (int)((log10(1.0*volume)-2)*5000); volume = max(min(volume, 0), -10000); *plVolume = volume; return S_OK; } // IAMOpenProgress STDMETHODIMP CRealMediaGraph::QueryProgress(LONGLONG* pllTotal, LONGLONG* pllCurrent) { *pllTotal = 100; *pllCurrent = m_pRMP->m_unPercentComplete > 0 ? m_pRMP->m_unPercentComplete : 100; // after seeking it drops to 0 and would show annoying "Buffering... (0%)" messages on the status line return S_OK; } // IGraphEngine STDMETHODIMP_(engine_t) CRealMediaGraph::GetEngine() { return RealMedia; }