/* * The MIT License (MIT) * * Copyright (c) Microsoft Corporation * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ ////////////////////////////////////////////////////////////////////////// // Playback helper class. ////////////////////////////////////////////////////////////////////////// // The success code S_FALSE deserves mention. Some methods use S_FALSE to mean, // roughly, a negative condition that is not a failure. It can also indicate a // "no-op" - the method succeeded, but had no effect. ////////////////////////////////////////////////////////////////////////// namespace VideoRenderers { template inline void SafeRelease(T*& p) { if (p) { p->Release(); p = NULL; } } // Forward declarations HRESULT CreateSourceStreamNode ( IMFMediaSource* pSource, IMFPresentationDescriptor* pSourcePD, IMFStreamDescriptor* pSourceSD, IMFTopologyNode** ppNode ); HRESULT CreateOutputNode ( IMFStreamDescriptor* pSourceSD, HWND hwndVideo, IMFTopologyNode** ppNode ); const char* EventName (MediaEventType type); #define CHECK_HR(hr) if (FAILED(hr)) goto done; enum { MSG_ID = WM_APP + 0x43f0 }; // wparam = IMFMediaEvent* enum State { Closed = 0, // No session. OpenPending, // Waiting for MESessionTopologyReady. SeekPending, // Waiting for MESessionScrubSampleComplete. ClosePending, // Waiting for MESessionClosed. Ready, // Ready to play. Started, // Session is playing a file. Paused, // Session is paused. Stopped // Session is stopped. }; class EVR : public IMFAsyncCallback { public: static HRESULT CreateInstance (EVR** ppPlayer); // IUnknown methods STDMETHODIMP QueryInterface (REFIID iid, void** ppv); STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release(); // IMFAsyncCallback methods STDMETHODIMP GetParameters (DWORD*, DWORD*) { // Implementation of this method is optional. return E_NOTIMPL; } STDMETHODIMP Invoke (IMFAsyncResult* pAsyncResult); // Playback HRESULT OpenURL (HWND hVideo, HWND hEvent, const WCHAR* sURL); HRESULT Play(); HRESULT Pause(); HRESULT Stop(); HRESULT Shutdown(); HRESULT HandleEvent (UINT_PTR pUnkPtr); State GetState() const { return m_state; } // Video functionality HRESULT Repaint(); HRESULT Resize(); Rectangle GetVideoSize(); double GetDuration(); double GetPosition(); void SetPosition (double seconds); double GetRate(); void SetRate (double rate); float GetVolume(); void SetVolume (float volume); protected: // Constructor is private. Use static CreateInstance method to instantiate. EVR(); // Destructor is private. Caller should call Release. virtual ~EVR(); HRESULT Initialize(); HRESULT CreateSession(); HRESULT CloseSession(); HRESULT StartPlayback(); HRESULT CreateMediaSource (const WCHAR* sURL); HRESULT CreateTopologyFromSource (IMFTopology** ppTopology); HRESULT AddBranchToPartialTopology ( IMFTopology* pTopology, IMFPresentationDescriptor* pSourcePD, DWORD iStream ); // Media event handlers HRESULT OnTopologyReady (IMFMediaEvent* pEvent); HRESULT OnSessionStarted (IMFMediaEvent* pEvent); HRESULT OnSessionPaused (IMFMediaEvent* pEvent); HRESULT OnSessionClosed (IMFMediaEvent* pEvent); HRESULT OnPresentationEnded (IMFMediaEvent* pEvent); LONG m_nRefCount; // Reference count. IMFMediaSession* m_pSession; IMFMediaSource* m_pSource; IMFVideoDisplayControl* m_pVideoDisplay; IMFSimpleAudioVolume* m_pSimpleAudio; IMFRateControl* m_pRateControl; IMFClock* m_pClock; HWND m_hwndVideo; // Video window. HWND m_hwndEvent; // App window to receive events. State m_state; // Current state of the media session. HANDLE m_hCloseEvent; // Event to wait on while closing JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EVR) }; /////////////////////////////////////////////////////////////////////// // Name: CreateInstance // Description: Static class method to create the EVR object. // // hVideo: Handle to the video window. // hEvent: Handle to the window to receive notifications. // ppPlayer: Receives an AddRef's pointer to the EVR object. // The caller must release the pointer. ///////////////////////////////////////////////////////////////////////// HRESULT EVR::CreateInstance (EVR** ppPlayer) { DBG ("EVR::Create"); if (ppPlayer == NULL) { return E_POINTER; } HRESULT hr = S_OK; EVR* pPlayer = new EVR(); if (pPlayer == NULL) { return E_OUTOFMEMORY; } hr = pPlayer->Initialize(); if (SUCCEEDED (hr)) { *ppPlayer = pPlayer; (*ppPlayer)->AddRef(); } // The EVR constructor sets the ref count to 1. // If the method succeeds, the caller receives an AddRef'd pointer. // Whether the method succeeds or fails, we must release the pointer. SafeRelease (pPlayer); return hr; } /////////////////////////////////////////////////////////////////////// // EVR constructor ///////////////////////////////////////////////////////////////////////// EVR::EVR() : m_nRefCount (1), m_pSession (NULL), m_pSource (NULL), m_pVideoDisplay (NULL), m_pSimpleAudio (NULL), m_pRateControl (NULL), m_pClock (NULL), m_hwndVideo (NULL), m_hwndEvent (NULL), m_state (Closed), m_hCloseEvent (NULL) { } /////////////////////////////////////////////////////////////////////// // EVR destructor ///////////////////////////////////////////////////////////////////////// EVR::~EVR() { jassert (m_pSession == NULL); // If FALSE, the app did not call Shutdown(). // Note: The application must call Shutdown() because the media // session holds a reference count on the EVR object. (This happens // when EVR calls IMediaEventGenerator::BeginGetEvent on the // media session.) As a result, there is a circular reference count // between the EVR object and the media session. Calling Shutdown() // breaks the circular reference count. // Note: If CreateInstance failed, the application will not call // Shutdown(). To handle that case, we must call Shutdown() in the // destructor. The circular ref-count problem does not occcur if // CreateInstance has failed. Also, calling Shutdown() twice is // harmless. Shutdown(); } ////////////////////////////////////////////////////////////////////// // Name: Initialize // Initializes the EVR object. This method is called by the // CreateInstance method. ///////////////////////////////////////////////////////////////////////// HRESULT EVR::Initialize() { HRESULT hr = S_OK; if (m_hCloseEvent) { return MF_E_ALREADY_INITIALIZED; } m_hCloseEvent = CreateEvent (NULL, FALSE, FALSE, NULL); if (m_hCloseEvent == NULL) { CHECK_HR (hr = HRESULT_FROM_WIN32 (GetLastError())); } done: return hr; } /////////////////////////////////////////////////////////////////////// // AddRef ///////////////////////////////////////////////////////////////////////// ULONG EVR::AddRef() { return InterlockedIncrement (&m_nRefCount); } /////////////////////////////////////////////////////////////////////// // Release ///////////////////////////////////////////////////////////////////////// ULONG EVR::Release() { ULONG uCount = InterlockedDecrement (&m_nRefCount); if (uCount == 0) { delete this; } // For thread safety, return a temporary variable. return uCount; } /////////////////////////////////////////////////////////////////////// // QueryInterface ///////////////////////////////////////////////////////////////////////// HRESULT EVR::QueryInterface (REFIID iid, void** ppv) { if (!ppv) { return E_POINTER; } if (iid == IID_IUnknown) { *ppv = static_cast (this); } else if (iid == IID_IMFAsyncCallback) { *ppv = static_cast (this); } else { *ppv = NULL; return E_NOINTERFACE; } AddRef(); return S_OK; } /////////////////////////////////////////////////////////////////////// // Name: OpenURL // Description: Opens a URL for playback. ///////////////////////////////////////////////////////////////////////// HRESULT EVR::OpenURL (HWND hVideo, HWND hEvent, const WCHAR* sURL) { DBG ("EVR::OpenURL"); DBG (String ("URL = ") + String (sURL)); jassert (hVideo != NULL); jassert (hEvent != NULL); m_hwndVideo = hVideo; m_hwndEvent = hEvent; // 1. Create a new media session. // 2. Create the media source. // 3. Create the topology. // 4. Queue the topology [asynchronous] // 5. Start playback [asynchronous - does not happen in this method.] HRESULT hr = S_OK; IMFTopology* pTopology = NULL; // Create the media session. CHECK_HR (hr = CreateSession()); // Create the media source. CHECK_HR (hr = CreateMediaSource (sURL)); // Create a partial topology. CHECK_HR (hr = CreateTopologyFromSource (&pTopology)); // Set the topology on the media session. CHECK_HR (hr = m_pSession->SetTopology (0, pTopology)); // Set our state to "open pending" m_state = OpenPending; // If SetTopology succeeded, the media session will queue an // MESessionTopologySet event. done: if (FAILED (hr)) { m_state = Closed; } SafeRelease (pTopology); return hr; } /////////////////////////////////////////////////////////////////////// // Name: Play // Description: Starts playback from paused state. ///////////////////////////////////////////////////////////////////////// HRESULT EVR::Play() { DBG ("EVR::Play"); if (m_state != Ready && m_state != Paused && m_state != Stopped) { return MF_E_INVALIDREQUEST; } if (m_pSession == NULL || m_pSource == NULL) { return E_UNEXPECTED; } HRESULT hr = StartPlayback(); return hr; } /////////////////////////////////////////////////////////////////////// // Name: Pause // Description: Pauses playback. ///////////////////////////////////////////////////////////////////////// HRESULT EVR::Pause() { DBG ("EVR::Pause"); if (m_state != Started) { return MF_E_INVALIDREQUEST; } if (m_pSession == NULL || m_pSource == NULL) { return E_UNEXPECTED; } HRESULT hr = m_pSession->Pause(); if (SUCCEEDED (hr)) { m_state = Paused; } return hr; } /////////////////////////////////////////////////////////////////////// // Name: Stop // Description: Stop playback. ///////////////////////////////////////////////////////////////////////// HRESULT EVR::Stop() { DBG ("EVR::Stop"); if (m_state != Started || m_state != Paused) { return MF_E_INVALIDREQUEST; } if (m_pSession == NULL || m_pSource == NULL) { return E_UNEXPECTED; } HRESULT hr = m_pSession->Stop(); if (SUCCEEDED (hr)) { m_state = Stopped; } return hr; } /////////////////////////////////////////////////////////////////////// // Name: Repaint // Description: Repaint the video window. // // Note: The application should call this method when it receives a // WM_PAINT message. ///////////////////////////////////////////////////////////////////////// HRESULT EVR::Repaint() { HRESULT hr = S_OK; if (m_pVideoDisplay) { hr = m_pVideoDisplay->RepaintVideo(); } return hr; } /////////////////////////////////////////////////////////////////////// // Name: Resize // Description: Repaint the video window. // // Note: The application should call this method when it receives a // WM_SIZE message. ///////////////////////////////////////////////////////////////////////// HRESULT EVR::Resize() { HRESULT hr = S_OK; if (m_pVideoDisplay) { // Set the destination rectangle. // Leave the default source rectangle (0,0,1,1). RECT rcDest = { 0, 0, 0, 0 }; GetClientRect (m_hwndVideo, &rcDest); hr = m_pVideoDisplay->SetVideoPosition (NULL, &rcDest); } return hr; } /////////////////////////////////////////////////////////////////////// // Name: GetVideoSize // Description: Get video size ///////////////////////////////////////////////////////////////////////// Rectangle EVR::GetVideoSize() { Rectangle rectangle; if (m_pVideoDisplay) { SIZE size = { 0, 0 }; HRESULT hr = m_pVideoDisplay->GetNativeVideoSize (&size, NULL); if (SUCCEEDED (hr)) rectangle.setSize ((int) size.cx, (int) size.cy); } return rectangle; } /////////////////////////////////////////////////////////////////////// // Name: GetDuration // Description: Get duration ///////////////////////////////////////////////////////////////////////// double EVR::GetDuration() { double duration = 0.0; if (m_pSource) { IMFPresentationDescriptor* pPD = NULL; HRESULT hr = m_pSource->CreatePresentationDescriptor (&pPD); if (SUCCEEDED (hr)) { // value unit is 100 nanoseconds UINT64 value = 0; hr = pPD->GetUINT64 (MF_PD_DURATION, &value); if (SUCCEEDED (hr)) duration = (double) value * 1.0e-7; SafeRelease (pPD); } } return duration; } /////////////////////////////////////////////////////////////////////// // Name: GetPosition // Description: Get position ///////////////////////////////////////////////////////////////////////// double EVR::GetPosition() { double position = 0.0; if (m_pClock) { LONGLONG clockTime = 0; MFTIME systemTime = 0; HRESULT hr = m_pClock->GetCorrelatedTime (0, &clockTime, &systemTime); if (SUCCEEDED (hr)) { // clockTime unit is 100 nanoseconds position = (double) clockTime * 1.0e-7; } } return position; } /////////////////////////////////////////////////////////////////////// // Name: SetPosition // Description: Set position (scrubbing) /////////////////////////////////////////////////////////////////////// // Scrubbing // Scrubbing is the process of instantaneously seeking to specific points in // the stream by interacting with a scrollbar, timeline, or other visual // representation of time. The term comes from the era of reel-to-reel tape // players when rocking a reel back and forth to locate a section was like // scrubbing the playback head with the tape. // // TODO Handle MESessionScrubSampleComplete media event. // // Scrubbing is performed to instantaneously seek to specific points within a // file by interacting with a visual representation of time, such as a // scrollbar. In Media Foundation, scrubbing means seeking on a file and // getting one updated frame. // // To perform scrubbing: // 1. Call MFGetService to get the IMFRateControl interface from the Media Session. // Do not get the IMFRateControl interface from the media source. Always // get the interface from the Media Session. // 2. Call IMFRateControl::SetRate to set the playback rate to zero. For more // information about calling this method, see How to Set the Playback Rate // on the Media Session. // 3. Create a seek position in a PROPVARIANT by specifying the presentation // time to seek to in a MFTIME type. // 4. Call IMFMediaSession::Start with the seek position to start playback. // 5. When the scrub operation is complete, the Media Session sends an // MESessionScrubSampleComplete event. Wait for this event before calling // Start again for another scrubbing operation. // // A successful scrubbing operation generates the MESessionScrubSampleComplete // event after all the stream sinks are updated with the new frame and the // scrubbing operation completes successfully. Scrubbing a video file displays // the frame that was seeked to, but does not generate an audio output. // // The application can perform frame stepping by setting the playback rate to // zero and then passing a PROPVARIANT that is set to VT_EMPTY in the call to // IMFMediaSession::Start. ///////////////////////////////////////////////////////////////////////// void EVR::SetPosition (double position) { if (m_pRateControl) { // seekTime unit is 100 nanoseconds INT64 seekTime = (INT64) (position * 1.0e+7); HRESULT hr = m_pRateControl->SetRate (FALSE, 0.0f); PROPVARIANT propvar; PropVariantInit (&propvar); // 8-byte signed integer propvar.vt = VT_I8; propvar.hVal.QuadPart = seekTime; if (SUCCEEDED (hr)) m_pSession->Start (NULL, &propvar); PropVariantClear (&propvar); } } /////////////////////////////////////////////////////////////////////// // Name: GetVolume // Description: Get audio volume ///////////////////////////////////////////////////////////////////////// float EVR::GetVolume() { float volume = 1.0f; if (m_pSimpleAudio) { float masterVolume = 1.0f; HRESULT hr = m_pSimpleAudio->GetMasterVolume (&masterVolume); if (SUCCEEDED (hr)) volume = masterVolume; } return volume; } /////////////////////////////////////////////////////////////////////// // Name: SetVolume // Description: Set audio volume ///////////////////////////////////////////////////////////////////////// void EVR::SetVolume (float volume) { if (m_pSimpleAudio) { float masterVolume = juce::jlimit (0.0f, 1.0f, volume); m_pSimpleAudio->SetMasterVolume (masterVolume); } } /////////////////////////////////////////////////////////////////////// // Name: GetRate // Description: Get playback rate ///////////////////////////////////////////////////////////////////////// double EVR::GetRate() { double rate = 1.0; if (m_pRateControl) { float currentRate = 1.0f; HRESULT hr = m_pRateControl->GetRate (NULL, ¤tRate); if (SUCCEEDED (hr)) rate = (double) currentRate; } return rate; } /////////////////////////////////////////////////////////////////////// // Name: SetRate // Descrirtion: Set playback rate /////////////////////////////////////////////////////////////////////// // Thinning // Thinning is any process that reduces the number of samples in a stream, to // reduce the overall bit rate. For video, thinning is generally accomplished // by dropping the delta frames and delivering only the key frames. Often the // pipeline can support faster playback rates using thinned playback, because // the data rate is lower because delta frames are not decoded. // // Thinning does not change the time stamps or durations on the samples. For // example, if the nominal rate of the video stream is 25 frames per second, // the duration of each frame is still marked as 40 milliseconds, even if the // media source is dropping all of the delta frames. That means there will be // a time gap between the end of one frame and the start of the next. ///////////////////////////////////////////////////////////////////////// void EVR::SetRate (double rate) { if (m_pRateControl) { if (fabs (rate) >= 0.5 && fabs (rate) <= 2.0) m_pRateControl->SetRate (FALSE, (float) rate); else m_pRateControl->SetRate (TRUE, (float) rate); } } /////////////////////////////////////////////////////////////////////// // Name: Invoke // Description: Callback for asynchronous BeginGetEvent method. // // pAsyncResult: Pointer to the result. ///////////////////////////////////////////////////////////////////////// HRESULT EVR::Invoke (IMFAsyncResult* pResult) { HRESULT hr = S_OK; MediaEventType meType = MEUnknown; // Event type IMFMediaEvent* pEvent = NULL; // Get the event from the event queue. CHECK_HR (hr = m_pSession->EndGetEvent (pResult, &pEvent)); // Get the event type. CHECK_HR (hr = pEvent->GetType (&meType)); // If the session is closed, the application is waiting on the // m_hCloseEvent event handle. Also, do not get any more // events from the session. if (meType == MESessionClosed) { SetEvent (m_hCloseEvent); } else { // For all other events, ask the media session for the // next event in the queue. CHECK_HR (hr = m_pSession->BeginGetEvent (this, NULL)); } // For most events, we post the event as a private window message to the // application. This lets the application process the event on it's // main thread. // However, if call to IMFMediaSession::Close is pending, it means the // application is waiting on the m_hCloseEvent event handle. (Blocking // call.) In that case, we simply discard the event. // NOTE: When IMFMediaSession::Close is called, MESessionClosed is NOT // necessarily the next event that we will receive. We may receive // any number of other events before receiving MESessionClosed. if (m_state != ClosePending) { // Leave a reference count on the event. pEvent->AddRef(); PostMessage (m_hwndEvent, MSG_ID, (WPARAM)pEvent, (LPARAM)0); } done: SafeRelease (pEvent); return S_OK; } //------------------------------------------------------------------- // HandleEvent // // Called by the application when it receives a MSG_ID message. // // This method is used to process media session events on the // application's main thread. // // pUnkPtr: Pointer to the IUnknown interface of a media session // event (IMFMediaEvent). //------------------------------------------------------------------- HRESULT EVR::HandleEvent (UINT_PTR pUnkPtr) { HRESULT hr = S_OK; HRESULT hrStatus = S_OK; // Event status MediaEventType meType = MEUnknown; // Event type MF_TOPOSTATUS TopoStatus = MF_TOPOSTATUS_INVALID; // Used with MESessionTopologyStatus event. IUnknown* pUnk = NULL; IMFMediaEvent* pEvent = NULL; // pUnkPtr is really an IUnknown pointer. pUnk = (IUnknown*) pUnkPtr; if (pUnk == NULL) { return E_POINTER; } CHECK_HR (hr = pUnk->QueryInterface (__uuidof (IMFMediaEvent), (void**) &pEvent)); // Get the event type. CHECK_HR (hr = pEvent->GetType (&meType)); // Get the event status. If the operation that triggered the event did // not succeed, the status is a failure code. CHECK_HR (hr = pEvent->GetStatus (&hrStatus)); DBG (EventName (meType)); // Check if the async operation succeeded. if (SUCCEEDED (hrStatus)) { // Switch on the event type. Update the internal state of the EVR as needed. switch (meType) { case MESessionTopologyStatus: // Get the status code. CHECK_HR (hr = pEvent->GetUINT32 (MF_EVENT_TOPOLOGY_STATUS, (UINT32*)&TopoStatus)); switch (TopoStatus) { case MF_TOPOSTATUS_READY: hr = OnTopologyReady (pEvent); break; default: // Nothing to do. break; } break; case MEEndOfPresentation: CHECK_HR (hr = OnPresentationEnded (pEvent)); break; } } else { hr = hrStatus; } done: SafeRelease (pUnk); SafeRelease (pEvent); return hr; } /////////////////////////////////////////////////////////////////////// // Name: Shutdown // Description: Releases all resources held by this object. ///////////////////////////////////////////////////////////////////////// HRESULT EVR::Shutdown() { DBG ("EVR::ShutDown"); HRESULT hr = S_OK; // Close the session hr = CloseSession(); if (m_hCloseEvent) { CloseHandle (m_hCloseEvent); m_hCloseEvent = NULL; } return hr; } /////////////////////////////////////////////////////////////////////// /// Protected methods /// /// All methods that follow are private to the EVR class. /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// // Name: OnTopologyReady // Description: Handler for MESessionTopologyReady event. // // Note: // - The MESessionTopologySet event means the session queued the // topology, but the topology is not ready yet. Generally, the // applicationno need to respond to this event unless there is an // error. // - The MESessionTopologyReady event means the new topology is // ready for playback. After this event is received, any calls to // IMFGetService will get service interfaces from the new topology. ///////////////////////////////////////////////////////////////////////// HRESULT EVR::OnTopologyReady (IMFMediaEvent* pEvent) { DBG ("EVR::OnTopologyReady"); // Ask for the IMFVideoDisplayControl interface. // This interface is implemented by the EVR and is // exposed by the media session as a service. // Note: This call is expected to fail if the source // does not have video. HRESULT hr = MFGetService ( m_pSession, MR_VIDEO_RENDER_SERVICE, __uuidof (IMFVideoDisplayControl), (void**)&m_pVideoDisplay ); if (SUCCEEDED (hr)) hr = StartPlayback(); if (SUCCEEDED (hr)) hr = m_pSession->GetClock (&m_pClock); if (SUCCEEDED (hr)) hr = MFGetService ( m_pSession, MR_POLICY_VOLUME_SERVICE, __uuidof (IMFSimpleAudioVolume), (void**)&m_pSimpleAudio ); if (SUCCEEDED (hr)) hr = MFGetService ( m_pSession, MF_RATE_CONTROL_SERVICE, __uuidof (IMFRateControl), (void**)&m_pRateControl ); return hr; } HRESULT EVR::OnPresentationEnded (IMFMediaEvent* pEvent) { DBG ("EVR::OnPresentationEnded"); // The session puts itself into the stopped state autmoatically. m_state = Stopped; return S_OK; } /////////////////////////////////////////////////////////////////////// // Name: CreateSession // Description: Creates a new instance of the media session. ///////////////////////////////////////////////////////////////////////// HRESULT EVR::CreateSession() { DBG ("EVR::CreateSession"); HRESULT hr = S_OK; // Close the old session, if any. CHECK_HR (hr = CloseSession()); jassert (m_state == Closed); // Create the media session. CHECK_HR (hr = MFCreateMediaSession (NULL, &m_pSession)); // Start pulling events from the media session CHECK_HR (hr = m_pSession->BeginGetEvent ((IMFAsyncCallback*) this, NULL)); done: return hr; } /////////////////////////////////////////////////////////////////////// // Name: CloseSession // Description: Closes the media session. // // Note: The IMFMediaSession::Close method is asynchronous, but the // EVR::CloseSession method waits on the MESessionClosed event. // The MESessionClosed event is guaranteed to be the last event // that the media session fires. ///////////////////////////////////////////////////////////////////////// HRESULT EVR::CloseSession() { HRESULT hr = S_OK; SafeRelease (m_pVideoDisplay); SafeRelease (m_pSimpleAudio); SafeRelease (m_pRateControl); SafeRelease (m_pClock); if (m_pSession) { DBG ("EVR::CloseSession"); DWORD dwWaitResult = 0; m_state = ClosePending; CHECK_HR (hr = m_pSession->Close()); // Wait for the close operation to complete dwWaitResult = WaitForSingleObject (m_hCloseEvent, 5000); if (dwWaitResult == WAIT_TIMEOUT) { DBG ("CloseSession timed out!"); } // Now there will be no more events from this session. } // Complete shutdown operations. // Shut down the media source. (Synchronous operation, no events.) if (m_pSource) { m_pSource->Shutdown(); } // Shut down the media session. (Synchronous operation, no events.) if (m_pSession) { m_pSession->Shutdown(); } SafeRelease (m_pSource); SafeRelease (m_pSession); m_state = Closed; done: return hr; } /////////////////////////////////////////////////////////////////////// // Name: StartPlayback // Description: Starts playback from the current position. ///////////////////////////////////////////////////////////////////////// HRESULT EVR::StartPlayback() { DBG ("EVR::StartPlayback"); jassert (m_pSession != NULL); HRESULT hr = S_OK; PROPVARIANT varStart; PropVariantInit (&varStart); varStart.vt = VT_EMPTY; hr = m_pSession->Start (&GUID_NULL, &varStart); if (SUCCEEDED (hr)) { // Note: Start is an asynchronous operation. However, we // can treat our state as being already started. If Start // fails later, we'll get an MESessionStarted event with // an error code, and we will update our state then. m_state = Started; } PropVariantClear (&varStart); return hr; } /////////////////////////////////////////////////////////////////////// // Name: CreateMediaSource // Description: Create a media source from a URL. // // sURL: The URL to open. ///////////////////////////////////////////////////////////////////////// HRESULT EVR::CreateMediaSource (const WCHAR* sURL) { DBG ("EVR::CreateMediaSource"); HRESULT hr = S_OK; MF_OBJECT_TYPE ObjectType = MF_OBJECT_INVALID; IMFSourceResolver* pSourceResolver = NULL; IUnknown* pSource = NULL; SafeRelease (m_pSource); // Create the source resolver. CHECK_HR (hr = MFCreateSourceResolver (&pSourceResolver)); // Use the source resolver to create the media source. // Note: For simplicity this sample uses the synchronous method on // IMFSourceResolver to create the media source. However, creating a // media source can take a noticeable amount of time, especially for // a network source. For a more responsive UI, use the asynchronous // BeginCreateObjectFromURL method. CHECK_HR (hr = pSourceResolver->CreateObjectFromURL ( sURL, // URL of the source. MF_RESOLUTION_MEDIASOURCE, // Create a source object. NULL, // Optional property store. &ObjectType, // Receives the created object type. &pSource // Receives a pointer to the media source. )); // Get the IMFMediaSource interface from the media source. CHECK_HR (hr = pSource->QueryInterface (__uuidof (IMFMediaSource), (void**) &m_pSource)); done: SafeRelease (pSourceResolver); SafeRelease (pSource); return hr; } /////////////////////////////////////////////////////////////////////// // CreateTopologyFromSource // Description: Create a playback topology from the media source. // // Pre-condition: The media source must be created already. // Call CreateMediaSource() before calling this method. ///////////////////////////////////////////////////////////////////////// HRESULT EVR::CreateTopologyFromSource (IMFTopology** ppTopology) { DBG ("EVR::CreateTopologyFromSource"); jassert (m_pSession != NULL); jassert (m_pSource != NULL); HRESULT hr = S_OK; IMFTopology* pTopology = NULL; IMFPresentationDescriptor* pSourcePD = NULL; DWORD cSourceStreams = 0; // Create a new topology. CHECK_HR (hr = MFCreateTopology (&pTopology)); // Create the presentation descriptor for the media source. CHECK_HR (hr = m_pSource->CreatePresentationDescriptor (&pSourcePD)); // Get the number of streams in the media source. CHECK_HR (hr = pSourcePD->GetStreamDescriptorCount (&cSourceStreams)); DBG (String ("Stream count: ") + String (cSourceStreams)); // For each stream, create the topology nodes and add them to the topology. for (DWORD i = 0; i < cSourceStreams; i++) { CHECK_HR (hr = AddBranchToPartialTopology (pTopology, pSourcePD, i)); } // Return the IMFTopology pointer to the caller. if (SUCCEEDED (hr)) { *ppTopology = pTopology; (*ppTopology)->AddRef(); } done: SafeRelease (pTopology); SafeRelease (pSourcePD); return hr; } /////////////////////////////////////////////////////////////////////// // Name: AddBranchToPartialTopology // Description: Adds a topology branch for one stream. // // pTopology: Pointer to the topology object. // pSourcePD: The source's presentation descriptor. // iStream: Index of the stream to render. // // Pre-conditions: The topology must be created already. // // Notes: For each stream, we must do the following: // 1. Create a source node associated with the stream. // 2. Create an output node for the renderer. // 3. Connect the two nodes. // The media session will resolve the topology, so we do not have // to worry about decoders or other transforms. ///////////////////////////////////////////////////////////////////////// HRESULT EVR::AddBranchToPartialTopology (IMFTopology* pTopology, IMFPresentationDescriptor* pSourcePD, DWORD iStream) { DBG ("EVR::AddBranchToPartialTopology"); jassert (pTopology != NULL); IMFStreamDescriptor* pSourceSD = NULL; IMFTopologyNode* pSourceNode = NULL; IMFTopologyNode* pOutputNode = NULL; BOOL fSelected = FALSE; HRESULT hr = S_OK; // Get the stream descriptor for this stream. CHECK_HR (hr = pSourcePD->GetStreamDescriptorByIndex (iStream, &fSelected, &pSourceSD)); // Create the topology branch only if the stream is selected. // Otherwise, do nothing. if (fSelected) { // Create a source node for this stream. CHECK_HR (hr = CreateSourceStreamNode (m_pSource, pSourcePD, pSourceSD, &pSourceNode)); // Create the output node for the renderer. CHECK_HR (hr = CreateOutputNode (pSourceSD, m_hwndVideo, &pOutputNode)); // Add both nodes to the topology. CHECK_HR (hr = pTopology->AddNode (pSourceNode)); CHECK_HR (hr = pTopology->AddNode (pOutputNode)); // Connect the source node to the output node. CHECK_HR (hr = pSourceNode->ConnectOutput (0, pOutputNode, 0)); } done: // Clean up. SafeRelease (pSourceSD); SafeRelease (pSourceNode); SafeRelease (pOutputNode); return hr; } /// Static functions //------------------------------------------------------------------- // Name: CreateSourceStreamNode // Description: Creates a source-stream node for a stream. // // pSource: Pointer to the media source that contains the stream. // pSourcePD: Presentation descriptor for the media source. // pSourceSD: Stream descriptor for the stream. // ppNode: Receives a pointer to the new node. //------------------------------------------------------------------- HRESULT CreateSourceStreamNode ( IMFMediaSource* pSource, IMFPresentationDescriptor* pSourcePD, IMFStreamDescriptor* pSourceSD, IMFTopologyNode** ppNode ) { if (!pSource || !pSourcePD || !pSourceSD || !ppNode) { return E_POINTER; } IMFTopologyNode* pNode = NULL; HRESULT hr = S_OK; // Create the source-stream node. CHECK_HR (hr = MFCreateTopologyNode (MF_TOPOLOGY_SOURCESTREAM_NODE, &pNode)); // Set attribute: Pointer to the media source. CHECK_HR (hr = pNode->SetUnknown (MF_TOPONODE_SOURCE, pSource)); // Set attribute: Pointer to the presentation descriptor. CHECK_HR (hr = pNode->SetUnknown (MF_TOPONODE_PRESENTATION_DESCRIPTOR, pSourcePD)); // Set attribute: Pointer to the stream descriptor. CHECK_HR (hr = pNode->SetUnknown (MF_TOPONODE_STREAM_DESCRIPTOR, pSourceSD)); // Return the IMFTopologyNode pointer to the caller. *ppNode = pNode; (*ppNode)->AddRef(); done: SafeRelease (pNode); return hr; } //------------------------------------------------------------------- // Name: CreateOutputNode // Description: Create an output node for a stream. // // pSourceSD: Stream descriptor for the stream. // ppNode: Receives a pointer to the new node. // // Notes: // This function does the following: // 1. Chooses a renderer based on the media type of the stream. // 2. Creates an IActivate object for the renderer. // 3. Creates an output topology node. // 4. Sets the IActivate pointer on the node. //------------------------------------------------------------------- HRESULT CreateOutputNode ( IMFStreamDescriptor* pSourceSD, HWND hwndVideo, IMFTopologyNode** ppNode ) { IMFTopologyNode* pNode = NULL; IMFMediaTypeHandler* pHandler = NULL; IMFActivate* pRendererActivate = NULL; GUID guidMajorType = GUID_NULL; HRESULT hr = S_OK; // Get the stream ID. DWORD streamID = 0; pSourceSD->GetStreamIdentifier (&streamID); // Just for debugging, ignore any failures. // Get the media type handler for the stream. CHECK_HR (hr = pSourceSD->GetMediaTypeHandler (&pHandler)); // Get the major media type. CHECK_HR (hr = pHandler->GetMajorType (&guidMajorType)); // Create a downstream node. CHECK_HR (hr = MFCreateTopologyNode (MF_TOPOLOGY_OUTPUT_NODE, &pNode)); // Create an IMFActivate object for the renderer, based on the media type. if (MFMediaType_Audio == guidMajorType) { // Create the audio renderer. DBG (String ("Audio stream: ") + String (streamID)); CHECK_HR (hr = MFCreateAudioRendererActivate (&pRendererActivate)); } else if (MFMediaType_Video == guidMajorType) { // Create the video renderer. DBG (String ("Video stream: ") + String (streamID)); CHECK_HR (hr = MFCreateVideoRendererActivate (hwndVideo, &pRendererActivate)); } else { DBG (String ("Unknown stream: ") + String (streamID)); CHECK_HR (hr = E_FAIL); } // Set the IActivate object on the output node. CHECK_HR (hr = pNode->SetObject (pRendererActivate)); // Return the IMFTopologyNode pointer to the caller. *ppNode = pNode; (*ppNode)->AddRef(); done: SafeRelease (pNode); SafeRelease (pHandler); SafeRelease (pRendererActivate); return hr; } const char* EventName (MediaEventType type) { switch (type) { case MEAudioSessionDeviceRemoved: return "MEAudioSessionDeviceRemoved: The audio device was removed."; case MEAudioSessionDisconnected: return "MEAudioSessionDisconnected: The audio session was disconnected from a Windows Terminal Services session"; case MEAudioSessionExclusiveModeOverride: return "MEAudioSessionExclusiveModeOverride: The audio session was preempted by an exclusive-mode connection."; case MEAudioSessionFormatChanged: return "MEAudioSessionFormatChanged: The default audio format for the audio device changed."; case MEAudioSessionGroupingParamChanged: return "MEAudioSessionGroupingParamChanged: The grouping parameters changed for the audio session."; case MEAudioSessionIconChanged: return "MEAudioSessionIconChanged: The audio session icon changed."; case MEAudioSessionNameChanged: return "MEAudioSessionNameChanged: The audio session display name changed."; case MEAudioSessionServerShutdown: return "MEAudioSessionServerShutdown: The Windows audio server system was shut down."; case MEAudioSessionVolumeChanged: return "MEAudioSessionVolumeChanged: The volume or mute state of the audio session changed"; case MEBufferingStarted: return "MEBufferingStarted: A media source started to buffer data."; case MEBufferingStopped: return "MEBufferingStopped: A media source stopped buffering data."; case MECaptureAudioSessionDeviceRemoved: return "MECaptureAudioSessionDeviceRemoved: The device was removed."; case MECaptureAudioSessionDisconnected: return "MECaptureAudioSessionDisconnected: The audio session is disconnected because the user logged off from a Windows Terminal Services (WTS) session."; case MECaptureAudioSessionExclusiveModeOverride: return "MECaptureAudioSessionExclusiveModeOverride: The user opened an audio stream in exclusive mode."; case MECaptureAudioSessionFormatChanged: return "MECaptureAudioSessionFormatChanged: The audio format changed."; case MECaptureAudioSessionServerShutdown: return "MECaptureAudioSessionServerShutdown: The audio session server shutdown."; case MECaptureAudioSessionVolumeChanged: return "MECaptureAudioSessionVolumeChanged: The volume changed."; case MEConnectEnd: return "MEConnectEnd: The network source finished opening a URL."; case MEConnectStart: return "MEConnectStart: The network source started opening a URL."; case MEContentProtectionMessage: return "MEContentProtectionMessage: The configuration changed for an output protection scheme."; case MEEnablerCompleted: return "MEEnablerCompleted: A content enabler object's action is complete."; case MEEnablerProgress: return "MEEnablerProgress: Signals the progress of a content enabler object."; case MEEndOfPresentation: return "MEEndOfPresentation: Raised by a media source when a presentation ends."; case MEEndOfPresentationSegment: return "MEEndOfPresentationSegment: Raised by the sequencer source when a segment is completed and is followed by another segment."; case MEEndOfStream: return "MEEndOfStream: Raised by a media stream when the stream ends."; case MEError: return "MEError: Signals a serious error."; case MEExtendedType: return "MEExtendedType: Custom event type."; case MEIndividualizationCompleted: return "MEIndividualizationCompleted: Individualization is complete."; case MEIndividualizationStart: return "MEIndividualizationStart: Individualization is about to begin."; case MELicenseAcquisitionCompleted: return "MELicenseAcquisitionCompleted: License acquisition is complete."; case MELicenseAcquisitionStart: return "MELicenseAcquisitionStart: License acquisition is about to begin."; case MEMediaSample: return "MEMediaSample: Raised when a media stream delivers a new sample."; case MENewPresentation: return "MENewPresentation: Raised by a media source a new presentation is ready."; case MENewStream: return "MENewStream: Raised by a media source when it starts a new stream."; case MENonFatalError: return "MENonFatalError: A non-fatal error occurred during streaming."; case MEPolicyChanged: return "MEPolicyChanged: The output policy for a stream changed."; case MEPolicyError: return "MEPolicyError: Raised by a trusted output if an error occurs while enforcing the output policy."; case MEPolicyReport: return "MEPolicyReport: Contains status information about the enforcement of an output policy."; case MEPolicySet: return "MEPolicySet: The IMFOutputTrustAuthority::SetPolicy method completed."; case MEQualityNotify: return "MEQualityNotify: Provides feedback about playback quality to the quality manager."; case MEReconnectEnd: return "MEReconnectEnd: Raised by a media source at the end of a reconnection attempt."; case MEReconnectStart: return "MEReconnectStart: Raised by a media source at the start of a reconnection attempt."; case MERendererEvent: return "MERendererEvent: Raised by the enhanced video renderer (EVR) when it receives a user event from the presenter."; case MESequencerSourceTopologyUpdated: return "MESequencerSourceTopologyUpdated: Raised by the sequencer source when the IMFSequencerSource::UpdateTopology method completes asynchronously."; case MESessionCapabilitiesChanged: return "MESessionCapabilitiesChanged: Raised by the Media Session when the session capabilities change."; case MESessionClosed: return "MESessionClosed: Raised when the IMFMediaSession::Close method completes asynchronously."; case MESessionEnded: return "MESessionEnded: Raised by the Media Session when it has finished playing the last presentation in the playback queue."; case MESessionNotifyPresentationTime: return "MESessionNotifyPresentationTime: Raised by the Media Session when a new presentation starts."; case MESessionPaused: return "MESessionPaused: Raised when the IMFMediaSession::Pause method completes asynchronously."; case MESessionRateChanged: return "MESessionRateChanged: Raised by the Media Session when the playback rate changes."; case MESessionScrubSampleComplete: return "MESessionScrubSampleComplete: Raised by the Media Session when it completes a scrubbing request."; case MESessionStarted: return "MESessionStarted: Raised when the IMFMediaSession::Start method completes asynchronously."; case MESessionStopped: return "MESessionStopped: Raised when the IMFMediaSession::Stop method completes asynchronously."; case MESessionStreamSinkFormatChanged: return "MESessionStreamSinkFormatChanged: Raised by the Media Session when the format changes on a media sink."; case MESessionTopologiesCleared: return "MESessionTopologiesCleared: Raised by the Media Session when the IMFMediaSession::ClearTopologies method completes asynchronously."; case MESessionTopologySet: return "MESessionTopologySet: Raised after the IMFMediaSession::SetTopology method completes asynchronously"; case MESessionTopologyStatus: return "MESessionTopologyStatus: Raised by the Media Session when the status of a topology changes."; case MESinkInvalidated: return "MESinkInvalidated: Raised when a media sink becomes invalid."; case MESourceCharacteristicsChanged: return "MESourceCharacteristicsChanged: Raised by a media source when the source's characteristics change."; case MESourceMetadataChanged: return "MESourceMetadataChanged: Raised by a media source when it updates its metadata."; case MESourcePaused: return "MESourcePaused: Raised by a media source when the IMFMediaSource::Pause method completes asynchronously."; case MESourceRateChanged: return "MESourceRateChanged: Raised by a media source when the playback rate changes."; case MESourceRateChangeRequested: return "MESourceRateChangeRequested: Raised by a media source to request a new playback rate."; case MESourceSeeked: return "MESourceSeeked: Raised when a media source seeks to a new position."; case MESourceStarted: return "MESourceStarted: Raised when a media source starts without seeking."; case MESourceStopped: return "MESourceStopped: Raised by a media source when the IMFMediaSource::Stop method completes asynchronously."; case MEStreamFormatChanged: return "MEStreamFormatChanged: Raised by a media stream when the media type of the stream changes."; case MEStreamPaused: return "MEStreamPaused: Raised by a media stream when the IMFMediaSource::Pause method completes asynchronously."; case MEStreamSeeked: return "MEStreamSeeked: Raised by a media stream after a call to IMFMediaSource::Start causes a seek in the stream."; case MEStreamSinkDeviceChanged: return "MEStreamSinkDeviceChanged: Raised by the stream sinks of the EVR if the video device changes."; case MEStreamSinkFormatChanged: return "MEStreamSinkFormatChanged: Raised by a stream sink when the sink's media type is no longer valid."; case MEStreamSinkMarker: return "MEStreamSinkMarker: Raised by a stream sink after the IMFStreamSink::PlaceMarker method is called."; case MEStreamSinkPaused: return "MEStreamSinkPaused: Raised by a stream sink when it completes the transition to the paused state."; case MEStreamSinkPrerolled: return "MEStreamSinkPrerolled: Raised by a stream sink when the stream has received enough preroll data to begin rendering."; case MEStreamSinkRateChanged: return "MEStreamSinkRateChanged: Raised by a stream sink when the rate has changed."; case MEStreamSinkRequestSample: return "MEStreamSinkRequestSample: Raised by a stream sink to request a new media sample from the pipeline."; case MEStreamSinkScrubSampleComplete: return "MEStreamSinkScrubSampleComplete: Raised by a stream sink when it completes a scrubbing request."; case MEStreamSinkStarted: return "MEStreamSinkStarted: Raised by a stream sink when it completes the transition to the running state."; case MEStreamSinkStopped: return "MEStreamSinkStopped: Raised by a stream sink when it completes the transition to the stopped state."; case MEStreamStarted: return "MEStreamStarted: Raised by a media stream when the source starts without seeking."; case MEStreamStopped: return "MEStreamStopped: Raised by a media stream when the IMFMediaSource::Stop method completes asynchronously."; case MEStreamThinMode: return "MEStreamThinMode: Raised by a media stream when it starts or stops thinning the stream."; case MEStreamTick: return "MEStreamTick: Signals that a media stream does not have data available at a specified time."; case METransformDrainComplete: return "METransformDrainComplete: Sent by an asynchronous Media Foundation transform (MFT) when a drain operation is complete."; case METransformHaveOutput: return "METransformHaveOutput: Sent by an asynchronous MFT when new output data is available from the MFT."; case METransformMarker: return "METransformMarker: Sent by an asynchronous MFT in response to an MFT_MESSAGE_COMMAND_MARKER message."; case METransformNeedInput: return "METransformNeedInput: Sent by an asynchronous MFT to request a new input sample."; case MEUnknown: return "MEUnknown: Unknown event type."; case MEUpdatedStream: return "MEUpdatedStream: Raised by a media source when it restarts or seeks a stream that is already active."; case MEVideoCaptureDevicePreempted: return "MEVideoCaptureDevicePreempted: The device has been preempted."; case MEVideoCaptureDeviceRemoved: return "MEVideoCaptureDeviceRemoved: The device has been removed."; default: break; }; return "MediaEventType: Unexpected media event type."; } }