/* * $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 #include "QuicktimeGraph.h" #include "IQTVideoSurface.h" #include "mplayerc.h" #include "MainFrm.h" #include "../../DSUtil/DSUtil.h" // // CQuicktimeGraph // #pragma warning(disable:4355) // 'this' : used in base member initializer list using namespace QT; CQuicktimeGraph::CQuicktimeGraph(HWND hWndParent, HRESULT& hr) : CBaseGraph() , m_wndDestFrame(this) , m_fQtInitialized(false) { hr = S_OK; DWORD dwStyle = WS_CHILD|WS_VISIBLE|WS_CLIPSIBLINGS|WS_CLIPCHILDREN; AppSettings& s = AfxGetAppSettings(); if(s.iQTVideoRendererType == VIDRNDT_QT_DX7) { if(SUCCEEDED(CreateAP7(CLSID_QT7AllocatorPresenter, hWndParent, &m_pQTAP))) dwStyle &= ~WS_VISIBLE; } else if(s.iQTVideoRendererType == VIDRNDT_QT_DX9) { bool bFullscreen = (AfxGetApp()->m_pMainWnd != NULL) && (((CMainFrame*)AfxGetApp()->m_pMainWnd)->IsD3DFullScreenMode()); if(SUCCEEDED(CreateAP9(CLSID_QT9AllocatorPresenter, hWndParent, bFullscreen, &m_pQTAP))) dwStyle &= ~WS_VISIBLE; } m_fQtInitialized = false; if(InitializeQTML(0) != 0) { hr = E_FAIL; return; } if(EnterMovies() != 0) { TerminateQTML(); hr = E_FAIL; return; } m_fQtInitialized = true; if(!m_wndWindowFrame.CreateEx(WS_EX_NOPARENTNOTIFY, NULL, NULL, dwStyle, CRect(0, 0, 0, 0), CWnd::FromHandle(hWndParent), 0)) { hr = E_FAIL; return; } if(!m_wndDestFrame.Create(NULL, NULL, dwStyle, CRect(0, 0, 0, 0), &m_wndWindowFrame, 0)) { hr = E_FAIL; return; } } CQuicktimeGraph::~CQuicktimeGraph() { m_wndDestFrame.DestroyWindow(); m_wndWindowFrame.DestroyWindow(); if(m_fQtInitialized) { ExitMovies(); TerminateQTML(); } } STDMETHODIMP CQuicktimeGraph::NonDelegatingQueryInterface(REFIID riid, void** ppv) { CheckPointer(ppv, E_POINTER); return QI(IVideoFrameStep) (m_pQTAP && (riid == __uuidof(ISubPicAllocatorPresenter) || riid == __uuidof(IQTVideoSurface))) ? m_pQTAP->QueryInterface(riid, ppv) : __super::NonDelegatingQueryInterface(riid, ppv); } // IGraphBuilder STDMETHODIMP CQuicktimeGraph::RenderFile(LPCWSTR lpcwstrFile, LPCWSTR lpcwstrPlayList) { bool fRet = m_wndDestFrame.OpenMovie(CString(lpcwstrFile)); if(fRet) { for(int i = 1, cnt = GetMovieTrackCount(m_wndDestFrame.theMovie); i <= cnt; i++) { Track aTrack = GetMovieIndTrack(m_wndDestFrame.theMovie, i); Media aMedia = GetTrackMedia(aTrack); OSType aTrackType; GetMediaHandlerDescription(aMedia, &aTrackType, 0, 0); if(aTrackType == SoundMediaType) { SampleDescriptionHandle aDesc = (SampleDescriptionHandle)NewHandle(sizeof(aDesc)); GetMediaSampleDescription(aMedia, 1, aDesc); if(GetMoviesError() == noErr) { SoundDescription& desc = **(SoundDescriptionHandle)aDesc; NotifyEvent(EC_BG_AUDIO_CHANGED, desc.numChannels, 0); i = cnt; } DisposeHandle((Handle)aDesc); } } } return fRet ? S_OK : E_FAIL; } // IMediaControl STDMETHODIMP CQuicktimeGraph::Run() { m_wndDestFrame.Run(); return S_OK; } STDMETHODIMP CQuicktimeGraph::Pause() { m_wndDestFrame.Pause(); return S_OK; } STDMETHODIMP CQuicktimeGraph::Stop() { m_wndDestFrame.Stop(); return S_OK; } STDMETHODIMP CQuicktimeGraph::GetState(LONG msTimeout, OAFilterState* pfs) { // TODO: this seems to deadlock when opening from the net return pfs ? *pfs = m_wndDestFrame.GetState(), S_OK : E_POINTER; } // IMediaSeeking STDMETHODIMP CQuicktimeGraph::GetDuration(LONGLONG* pDuration) { CheckPointer(pDuration, E_POINTER); *pDuration = 0; if(!m_wndDestFrame.theMovie) return E_UNEXPECTED; TimeScale ts = GetMovieTimeScale(m_wndDestFrame.theMovie); if(ts == 0) return E_FAIL; *pDuration = 10000i64*GetMovieDuration(m_wndDestFrame.theMovie)/ts*1000; return S_OK; } STDMETHODIMP CQuicktimeGraph::GetCurrentPosition(LONGLONG* pCurrent) { CheckPointer(pCurrent, E_POINTER); *pCurrent = 0; if(!m_wndDestFrame.theMovie) return E_UNEXPECTED; TimeScale ts = GetMovieTimeScale(m_wndDestFrame.theMovie); if(ts == 0) return E_FAIL; TimeRecord tr; *pCurrent = 10000i64*GetMovieTime(m_wndDestFrame.theMovie, &tr)/ts*1000; return S_OK; } STDMETHODIMP CQuicktimeGraph::SetPositions(LONGLONG* pCurrent, DWORD dwCurrentFlags, LONGLONG* pStop, DWORD dwStopFlags) { CheckPointer(pCurrent, E_POINTER); if(!(dwCurrentFlags&AM_SEEKING_AbsolutePositioning)) return E_INVALIDARG; if(!m_wndDestFrame.theMovie) return E_UNEXPECTED; TimeScale ts = GetMovieTimeScale(m_wndDestFrame.theMovie); if(ts == 0) return E_FAIL; SetMovieTimeValue(m_wndDestFrame.theMovie, (TimeValue)(*pCurrent*ts/1000/10000i64)); if(!m_wndDestFrame.theMC) { UpdateMovie(m_wndDestFrame.theMovie); MoviesTask(m_wndDestFrame.theMovie, 0L); } return S_OK; } STDMETHODIMP CQuicktimeGraph::SetRate(double dRate) { return m_wndDestFrame.theMovie ? SetMovieRate(m_wndDestFrame.theMovie, (Fixed)(dRate * 0x10000)), S_OK : E_UNEXPECTED; } STDMETHODIMP CQuicktimeGraph::GetRate(double* pdRate) { CheckPointer(pdRate, E_POINTER); *pdRate = 1.0; return m_wndDestFrame.theMovie ? *pdRate = (double)GetMovieRate(m_wndDestFrame.theMovie) / 0x10000, S_OK : E_UNEXPECTED; } // IVideoWindow STDMETHODIMP CQuicktimeGraph::SetWindowPosition(long Left, long Top, long Width, long Height) { if(IsWindow(m_wndWindowFrame.m_hWnd)) m_wndWindowFrame.MoveWindow(Left, Top, Width, Height); return S_OK; } // IBasicVideo STDMETHODIMP CQuicktimeGraph::SetDestinationPosition(long Left, long Top, long Width, long Height)// {return E_NOTIMPL;} { if(!m_pQTAP && IsWindow(m_wndDestFrame.m_hWnd)) { m_wndDestFrame.MoveWindow(Left, Top, Width, Height); if(m_wndDestFrame.theMC) { Rect bounds = {0,0,(short)Height,(short)Width}; MCPositionController(m_wndDestFrame.theMC, &bounds, NULL, mcTopLeftMovie|mcScaleMovieToFit); } } return S_OK; } STDMETHODIMP CQuicktimeGraph::GetVideoSize(long* pWidth, long* pHeight) { if(!pWidth || !pHeight) return E_POINTER; *pWidth = m_wndDestFrame.m_size.cx; *pHeight = m_wndDestFrame.m_size.cy; return S_OK; } // IBasicAudio STDMETHODIMP CQuicktimeGraph::put_Volume(long lVolume) { if(m_wndDestFrame.theMovie) { lVolume = (lVolume == -10000) ? 0 : (int)pow(10.0, (double)lVolume/4152.41 + 2.41); SetMovieVolume(m_wndDestFrame.theMovie, (short)max(min(lVolume, 256), 0)); return S_OK; } return E_UNEXPECTED; } STDMETHODIMP CQuicktimeGraph::get_Volume(long* plVolume) { CheckPointer(plVolume, E_POINTER); if(m_wndDestFrame.theMovie) { long lVolume = (long)GetMovieVolume(m_wndDestFrame.theMovie); *plVolume = (int)((log10(1.0*lVolume)-2.41)*4152.41); *plVolume = max(min(*plVolume, 0), -10000); return S_OK; } return E_UNEXPECTED; } // IVideoFrameStep STDMETHODIMP CQuicktimeGraph::Step(DWORD dwFrames, IUnknown* pStepObject) { if(pStepObject) return E_INVALIDARG; if(dwFrames == 0) return S_OK; if(!m_wndDestFrame.theMovie) return E_UNEXPECTED; // w/o m_wndDestFrame.theMC OSType myTypes[] = {VisualMediaCharacteristic}; TimeValue myCurrTime = GetMovieTime(m_wndDestFrame.theMovie, NULL); Fixed theRate = (int)dwFrames > 0 ? 0x00010000 : 0xffff0000; for(int nSteps = abs((int)dwFrames); nSteps > 0; nSteps--) { TimeValue myNextTime; GetMovieNextInterestingTime(m_wndDestFrame.theMovie, nextTimeStep, 1, myTypes, myCurrTime, theRate, &myNextTime, NULL); if(GetMoviesError() != noErr) return E_FAIL; myCurrTime = myNextTime; } if(myCurrTime >= 0 && myCurrTime < GetMovieDuration(m_wndDestFrame.theMovie)) { SetMovieTimeValue(m_wndDestFrame.theMovie, myCurrTime); if(GetMoviesError() != noErr) return E_FAIL; // the rest is not needed when we also have m_wndDestFrame.theMC: UpdateMovie(m_wndDestFrame.theMovie); if(GetMoviesError() != noErr) return E_FAIL; MoviesTask(m_wndDestFrame.theMovie, 0L); } NotifyEvent(EC_STEP_COMPLETE); return S_OK; /* // w/ m_wndDestFrame.theMC short myStep = (short)(long)dwFrames; return noErr == MCDoAction(m_wndDestFrame.theMC, mcActionStep, (Ptr)myStep) ? NotifyEvent(EC_STEP_COMPLETE), S_OK : E_FAIL; */ } STDMETHODIMP CQuicktimeGraph::CanStep(long bMultiple, IUnknown* pStepObject) { return m_wndDestFrame.theMovie ? S_OK : S_FALSE; } STDMETHODIMP CQuicktimeGraph::CancelStep() { return E_NOTIMPL; } // IGraphEngine STDMETHODIMP_(engine_t) CQuicktimeGraph::GetEngine() { return QuickTime; } // // CQuicktimeWindow // CQuicktimeWindow::CQuicktimeWindow(CQuicktimeGraph* pGraph) : m_pGraph(pGraph) , theMovie(NULL) , theMC(NULL) , m_size(0, 0) , m_idEndPoller(0) , m_fs(State_Stopped) , m_offscreenGWorld(NULL) { } void CQuicktimeWindow::ProcessMovieEvent(unsigned int message, unsigned int wParam, long lParam) { if(message >= WM_MOUSEFIRST && message <= WM_MOUSELAST || message >= WM_KEYFIRST && message <= WM_KEYLAST) return; // Convert the Windows event to a QTML event MSG theMsg; EventRecord macEvent; LONG thePoints = GetMessagePos(); theMsg.hwnd = m_hWnd; theMsg.message = message; theMsg.wParam = wParam; theMsg.lParam = lParam; theMsg.time = GetMessageTime(); theMsg.pt.x = LOWORD(thePoints); theMsg.pt.y = HIWORD(thePoints); // tranlate a windows event to a mac event WinEventToMacEvent(&theMsg, &macEvent); // Pump messages as mac event MCIsPlayerEvent(theMC, (const EventRecord*)&macEvent); } LRESULT CQuicktimeWindow::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { if(message == WM_ERASEBKGND) { LRESULT theResult = __super::WindowProc(message, wParam, lParam); ProcessMovieEvent(message, wParam, lParam); return theResult; } else { ProcessMovieEvent(message, wParam, lParam); return __super::WindowProc(message, wParam, lParam); } } OSErr CQuicktimeWindow::MyMovieDrawingCompleteProc(Movie theMovie, long refCon) { CQuicktimeWindow* pQW = (CQuicktimeWindow*)refCon; if(!pQW) return noErr; CQuicktimeGraph* pGraph = pQW->m_pGraph; if(!pGraph) return noErr; if(CComQIPtr pQTVS = (IUnknown*)(INonDelegatingUnknown*)pGraph) { BITMAP bm; pQW->m_bm.GetObject(sizeof(bm), &bm); bm.bmWidth = pQW->m_size.cx; pQTVS->DoBlt(bm); } /* else { pQW->Invalidate(); }*/ return(noErr); } bool CQuicktimeWindow::OpenMovie(CString fn) { CloseMovie(); CComQIPtr pQTVS = (IUnknown*)(INonDelegatingUnknown*)m_pGraph; if(!pQTVS) { // Set the port SetGWorld((CGrafPtr)GetHWNDPort(m_hWnd), NULL); } if(fn.Find(_T("://")) > 0) { Handle myHandle = NULL; Size mySize = fn.GetLength()+1; myHandle = NewHandleClear(mySize); if(!myHandle) return(false); BlockMove((LPSTR)(LPCSTR)CStringA(fn), *myHandle, mySize); OSErr err = NewMovieFromDataRef(&theMovie, newMovieActive, NULL, myHandle, URLDataHandlerSubType); DisposeHandle(myHandle); if(err != noErr) return(false); } else { if(!(fn.GetLength() > 0 && fn.GetLength() < 255)) return(false); CHAR buff[_MAX_PATH] = {0, 0}; #ifdef UNICODE WideCharToMultiByte(GetACP(), 0, fn, -1, buff+1, MAX_PATH-1, 0, 0); #else strcpy(buff+1, fn); #endif buff[0] = strlen(buff+1); // Make a FSSpec with a pascal string filename FSSpec sfFile; FSMakeFSSpec(0, 0L, (BYTE*)buff, &sfFile); // Open the movie file short movieResFile; OSErr err = OpenMovieFile(&sfFile, &movieResFile, fsRdPerm); if(err == noErr) { err = NewMovieFromFile(&theMovie, movieResFile, 0, 0, newMovieActive, 0); CloseMovieFile(movieResFile); } if(err != noErr) return(false); } Rect rect; GetMovieBox(theMovie, &rect); MacOffsetRect(&rect, -rect.left, -rect.top); SetMovieBox(theMovie, &rect); m_size.SetSize(rect.right - rect.left, rect.bottom - rect.top); Rect nrect; GetMovieNaturalBoundsRect(theMovie, &nrect); if(!pQTVS) { theMC = NewMovieController(theMovie, &rect, mcTopLeftMovie|mcNotVisible); } else if(m_size.cx > 0 && m_size.cy > 0) { SetMovieDrawingCompleteProc(theMovie, movieDrawingCallWhenChanged,//|movieDrawingCallAlways, MyMovieDrawingCompleteProc, (long)this); if(CDC* pDC = GetDC()) { m_dc.CreateCompatibleDC(pDC); ReleaseDC(pDC); struct { BITMAPINFOHEADER bmiHeader; long bmiColors[256]; } bmi; memset(&bmi, 0, sizeof(bmi)); //int bpp = m_dc.GetDeviceCaps(BITSPIXEL); bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biCompression = BI_BITFIELDS/*BI_RGB*/; LONG pitch = (m_size.cx + 0xF) & ~0xF; bmi.bmiHeader.biWidth = pitch; bmi.bmiHeader.biHeight = -m_size.cy; bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32/*bpp*/; bmi.bmiColors[0] = /*bpp == 16 ? 0xf800 :*/ 0xff0000; bmi.bmiColors[1] = /*bpp == 16 ? 0x07e0 :*/ 0x00ff00; bmi.bmiColors[2] = /*bpp == 16 ? 0x001f :*/ 0x0000ff; void* bits; m_bm.Attach(CreateDIBSection(m_dc, (BITMAPINFO*)&bmi, DIB_RGB_COLORS, &bits, NULL, 0)); QDErr err = NewGWorldFromHBITMAP(&m_offscreenGWorld, NULL, NULL, 0, m_bm.m_hObject, m_dc.m_hDC); UNUSED_ALWAYS(err); SetMovieGWorld(theMovie, m_offscreenGWorld, GetGWorldDevice(m_offscreenGWorld)); BITMAP bm; m_bm.GetObject(sizeof(bm), &bm); bm.bmWidth = m_size.cx; pQTVS->BeginBlt(bm); } } return(theMovie != NULL); } void CQuicktimeWindow::CloseMovie() { if(theMC) DisposeMovieController(theMC), theMC = NULL; if(theMovie) DisposeMovie(theMovie), theMovie = NULL; m_size.SetSize(0, 0); m_fs = State_Stopped; if(m_offscreenGWorld) DisposeGWorld(m_offscreenGWorld), m_offscreenGWorld = NULL; m_dc.DeleteDC(); m_bm.DeleteObject(); } void CQuicktimeWindow::Run() { if(theMovie) { StartMovie(theMovie); if(!m_idEndPoller) m_idEndPoller = SetTimer(1, 10, NULL); // 10ms -> 100fps max } m_fs = State_Running; } void CQuicktimeWindow::Pause() { if(theMovie) { StopMovie(theMovie); if(m_idEndPoller) KillTimer(m_idEndPoller), m_idEndPoller = 0; } m_fs = State_Paused; } void CQuicktimeWindow::Stop() { if(theMovie) { StopMovie(theMovie); GoToBeginningOfMovie(theMovie); if(m_idEndPoller) KillTimer(m_idEndPoller), m_idEndPoller = 0; } m_fs = State_Stopped; } FILTER_STATE CQuicktimeWindow::GetState() { return m_fs; } BEGIN_MESSAGE_MAP(CQuicktimeWindow, CPlayerWindow) ON_WM_CREATE() ON_WM_DESTROY() ON_WM_ERASEBKGND() ON_WM_TIMER() END_MESSAGE_MAP() int CQuicktimeWindow::OnCreate(LPCREATESTRUCT lpCreateStruct) { if(__super::OnCreate(lpCreateStruct) == -1) return -1; CComQIPtr pQTVS = (IUnknown*)(INonDelegatingUnknown*)m_pGraph; if(!pQTVS) { // Create GrafPort <-> HWND association CreatePortAssociation(m_hWnd, NULL, 0); } return 0; } void CQuicktimeWindow::OnDestroy() { CPlayerWindow::OnDestroy(); // close any movies before destroying PortAssocation CloseMovie(); CComQIPtr pQTVS = (IUnknown*)(INonDelegatingUnknown*)m_pGraph; if(!pQTVS) { // Destroy the view's GrafPort <-> HWND association if(m_hWnd) if(CGrafPtr windowPort = (CGrafPtr)GetHWNDPort(m_hWnd)) DestroyPortAssociation(windowPort); } } BOOL CQuicktimeWindow::OnEraseBkgnd(CDC* pDC) { return m_fs != State_Stopped && theMovie ? TRUE : __super::OnEraseBkgnd(pDC); } void CQuicktimeWindow::OnTimer(UINT_PTR nIDEvent) { if(nIDEvent == m_idEndPoller && theMovie) { if(IsMovieDone(theMovie)) { Pause(); m_pGraph->NotifyEvent(EC_COMPLETE); } else if(CComQIPtr pQTVS = (IUnknown*)(INonDelegatingUnknown*)m_pGraph) { MoviesTask(theMovie, 0); /* long duration = 0, scale = 1000; OSErr err = QTGetTimeUntilNextTask(&duration, scale); // err is 0 but still doesn't seem to work... returns duration=0 always TRACE(_T("%d\n"), duration); KillTimer(m_idEndPoller); m_idEndPoller = SetTimer(m_idEndPoller, duration, NULL); */ } } __super::OnTimer(nIDEvent); }