昨日作ったコードはやっぱりBugっていた。修正したコードは以下。まだBugは潜んでいそうだなー。
player.h
// 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) Microsoft Corporation. All rights reserved.
#ifndef PLAYER_H
#define PLAYER_H
#include <new>
#include <windows.h>
#include <shobjidl.h>
#include <shlwapi.h>
#include <assert.h>
#include <strsafe.h>
// Media Foundation headers
#include <mfapi.h>
#include <mfidl.h>
#include <mferror.h>
#include <evr.h>
#include "resource.h"
_WRL_PTR_TYPEDEF(IMFMediaSession);
_WRL_PTR_TYPEDEF(IMFMediaSource);
_WRL_PTR_TYPEDEF(IMFVideoDisplayControl);
_WRL_PTR_TYPEDEF(IMFTopology);
_WRL_PTR_TYPEDEF(IMFPresentationDescriptor);
_WRL_PTR_TYPEDEF(IMFMediaEvent);
_WRL_PTR_TYPEDEF(IMFSourceResolver);
_WRL_PTR_TYPEDEF(IMFStreamDescriptor);
_WRL_PTR_TYPEDEF(IMFTopologyNode);
_WRL_PTR_TYPEDEF(IMFActivate);
_WRL_PTR_TYPEDEF(IMFMediaTypeHandler);
//_WRL_PTR_TYPEDEF();
//_WRL_PTR_TYPEDEF();
namespace sf {
template <class T> void SafeRelease(T **ppT)
{
if (*ppT)
{
(*ppT)->Release();
*ppT = NULL;
}
}
const UINT WM_APP_PLAYER_EVENT = WM_APP + 1;
// WPARAM = IMFMediaEvent*, WPARAM = MediaEventType
enum PlayerState
{
Closed = 0, // No session.
Ready, // Session was created, ready to open a file.
OpenPending, // Session is opening a file.
Started, // Session is playing a file.
Paused, // Session is paused.
Stopped, // Session is stopped (ready to play).
Closing // Application has closed the session, but is waiting for MESessionClosed.
};
struct prop_variant
{
prop_variant()
{
PropVariantInit(&value_);
}
~prop_variant()
{
PropVariantClear(&value_);
}
PROPVARIANT* get(){ return &value_;};
PROPVARIANT* operator &(){return get();}
operator PROPVARIANT*() {return get();}
private:
PROPVARIANT value_;
};
class Player;
typedef Microsoft::WRL::ComPtr<Player> PlayerPtr;
PlayerPtr CreatePlayer(HWND hVideo, HWND hEvent);
class Player : public IMFAsyncCallback
{
friend PlayerPtr CreatePlayer(HWND hVideo, HWND hEvent);
public:
// 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
void OpenURL(const std::wstring& sURL);
void Play();
void Pause();
void Stop();
void Shutdown();
void HandleEvent(UINT_PTR pUnkPtr);
PlayerState GetState() const { return m_state; }
// Video functionality
void Repaint();
void ResizeVideo(WORD width, WORD height);
BOOL HasVideo() const { return (m_pVideoDisplay != NULL); }
protected:
// Constructor is private. Use static CreateInstance method to instantiate.
Player(HWND hVideo, HWND hEvent);
// Destructor is private. Caller should call Release.
virtual ~Player();
void Initialize();
void CreateSession();
void CloseSession();
void StartPlayback();
// Media event handlers
virtual void OnTopologyStatus(IMFMediaEventPtr pEvent);
virtual void OnPresentationEnded(IMFMediaEventPtr pEvent);
virtual void OnNewPresentation(IMFMediaEventPtr pEvent);
// Override to handle additional session events.
virtual void OnSessionEvent(IMFMediaEventPtr, MediaEventType)
{
//return S_OK;
}
protected:
long m_nRefCount; // Reference count.
IMFMediaSessionPtr m_pSession;
IMFMediaSourcePtr m_pSource;
IMFVideoDisplayControlPtr m_pVideoDisplay;
HWND m_hwndVideo; // Video window.
HWND m_hwndEvent; // App window to receive events.
PlayerState m_state; // Current state of the media session.
HANDLE m_hCloseEvent; // Event to wait on while closing.
};
inline void intrusive_ptr_add_ref(Player *p)
{
p->AddRef();
};
inline void intrusive_ptr_release(Player *p)
{
p->Release();
};
}
#endif PLAYER_H
player.cpp
// 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) Microsoft Corporation. All rights reserved.
#include "stdafx.h"
#include "Player.h"
#include <assert.h>
#if _DEBUG
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#define new new(_NORMAL_BLOCK, __FILE__, __LINE__)
#endif
#pragma comment(lib, "shlwapi")
#pragma comment(lib,"Mfplat.lib")
#pragma comment(lib,"Mf.lib")
#pragma comment(lib,"Mfuuid.lib")
#pragma comment(lib,"Strmiids.lib")
//Mf.lib Mfidl.h
namespace sf {
template <class Q>
HRESULT GetEventObject(IMFMediaEvent *pEvent, Q **ppObject)
{
*ppObject = NULL; // zero output
PROPVARIANT var;
HRESULT hr = pEvent->GetValue(&var);
if (SUCCEEDED(hr))
{
if (var.vt == VT_UNKNOWN)
{
hr = var.punkVal->QueryInterface(ppObject);
}
else
{
hr = MF_E_INVALIDTYPE;
}
PropVariantClear(&var);
}
return hr;
}
IMFMediaSourcePtr CreateMediaSource(const std::wstring& pszURL);
IMFTopologyPtr CreatePlaybackTopology(IMFMediaSourcePtr pSource,
IMFPresentationDescriptorPtr pPD, HWND hVideoWnd);
// Static class method to create the Player object.
PlayerPtr CreatePlayer(
HWND hVideo, // Video window.
HWND hEvent // Window to receive notifications.
)
{
PlayerPtr p(new Player(hVideo, hEvent));
if (!p)
{
throw win32_error_exception(E_OUTOFMEMORY);
}
p->Initialize();
return p;
}
void Player::Initialize()
{
// Start up Media Foundation platform.
THROW_IF_ERR(MFStartup(MF_VERSION));
m_hCloseEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
THROW_IF_ERR(HRESULT_FROM_WIN32(GetLastError()));
}
Player::Player(HWND hVideo, HWND hEvent) :
m_hwndVideo(hVideo),
m_hwndEvent(hEvent),
m_state(Closed),
m_hCloseEvent(NULL),
m_nRefCount(0)
{
}
Player::~Player()
{
assert(m_pSession.Get() == NULL);
// If FALSE, the app did not call Shutdown().
// When Player calls IMediaEventGenerator::BeginGetEvent on the
// media session, it causes the media session to hold a reference
// count on the Player.
// This creates a circular reference count between Player and the
// media session. Calling Shutdown breaks the circular reference
// count.
// If CreateInstance fails, the application will not call
// Shutdown. To handle that case, call Shutdown in the destructor.
Shutdown();
}
// IUnknown methods
HRESULT Player::QueryInterface(REFIID riid, void** ppv)
{
static const QITAB qit[] =
{
QITABENT(Player, IMFAsyncCallback),
{ 0 }
};
return QISearch(this, qit, riid, ppv);
}
ULONG Player::AddRef()
{
return InterlockedIncrement(&m_nRefCount);
}
ULONG Player::Release()
{
ULONG uCount = InterlockedDecrement(&m_nRefCount);
if (uCount == 0)
{
delete this;
}
return uCount;
}
// Open a URL for playback.
void Player::OpenURL(const std::wstring& sURL)
{
// 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.]
IMFTopologyPtr pTopology;
IMFPresentationDescriptorPtr pSourcePD;
// Create the media session.
CreateSession();
// Create the media source.
m_pSource = CreateMediaSource(sURL);
// Create the presentation descriptor for the media source.
THROW_IF_ERR(m_pSource->CreatePresentationDescriptor(&pSourcePD));
// Create a partial topology.
pTopology = CreatePlaybackTopology(m_pSource, pSourcePD, m_hwndVideo);
// Set the topology on the media session.
THROW_IF_ERR(m_pSession->SetTopology(0, pTopology.Get()));
m_state = OpenPending;
// If SetTopology succeeds, the media session will queue an
// MESessionTopologySet event.
}
// Pause playback.
void Player::Pause()
{
if (m_state != Started)
{
throw win32_error_exception(MF_E_INVALIDREQUEST);
}
if (!m_pSession || !m_pSource)
{
throw win32_error_exception(E_UNEXPECTED);
}
THROW_IF_ERR(m_pSession->Pause());
m_state = Paused;
}
// Stop playback.
void Player::Stop()
{
if (m_state != Started && m_state != Paused)
{
throw win32_error_exception( MF_E_INVALIDREQUEST );
}
if (!m_pSession)
{
throw win32_error_exception( E_UNEXPECTED);
}
THROW_IF_ERR(m_pSession->Stop());
}
// Repaint the video window. Call this method on WM_PAINT.
void Player::Repaint()
{
if (m_pVideoDisplay)
{
m_pVideoDisplay->RepaintVideo();
}
}
// Resize the video rectangle.
//
// Call this method if the size of the video window changes.
void Player::ResizeVideo(WORD width, WORD height)
{
if (m_pVideoDisplay)
{
// Set the destination rectangle.
// Leave the default source rectangle (0,0,1,1).
RECT rcDest = { 0, 0, width, height };
THROW_IF_ERR(m_pVideoDisplay->SetVideoPosition(NULL, &rcDest));
}
}
// Callback for the asynchronous BeginGetEvent method.
HRESULT Player::Invoke(IMFAsyncResult *pResult)
{
MediaEventType meType = MEUnknown; // Event type
IMFMediaEventPtr pEvent;
try {
// Get the event from the event queue.
THROW_IF_ERR(m_pSession->EndGetEvent(pResult, pEvent.GetAddressOf()));
// Get the event type.
THROW_IF_ERR(pEvent->GetType(&meType));
if (meType == MESessionClosed)
{
// The session was closed.
// The application is waiting on the m_hCloseEvent event handle.
SetEvent(m_hCloseEvent);
}
else
{
// For all other events, get the next event in the queue.
THROW_IF_ERR(m_pSession->BeginGetEvent(this, NULL));
}
// Check the application state.
// If a call to IMFMediaSession::Close is pending, it means the
// application is waiting on the m_hCloseEvent event and
// the application's message loop is blocked.
// Otherwise, post a private window message to the application.
if (m_state != Closing)
{
// Leave a reference count on the event.
PostMessage(m_hwndEvent, WM_APP_PLAYER_EVENT,
(WPARAM)pEvent.Get(), (LPARAM)meType);
}
return S_OK;
} catch (win32_error_exception& e)
{
return e.hresult();
}
}
void Player::HandleEvent(UINT_PTR pEventPtr)
{
MediaEventType meType = MEUnknown;
IMFMediaEventPtr pEvent = (IMFMediaEvent*)pEventPtr;
pEvent.Attach(reinterpret_cast<IMFMediaEvent*>(pEventPtr));
if (!pEvent)
{
throw win32_error_exception(E_POINTER);
}
// Get the event type.
THROW_IF_ERR(pEvent->GetType(&meType));
// Get the event status. If the operation that triggered the event
// did not succeed, the status is a failure code.
HRESULT hrStatus = S_OK;
THROW_IF_ERR(pEvent->GetStatus(&hrStatus));
// Check if the async operation succeeded.
THROW_IF_ERR(hrStatus);
switch(meType)
{
case MESessionTopologyStatus:
OnTopologyStatus(pEvent);
break;
case MEEndOfPresentation:
OnPresentationEnded(pEvent);
break;
case MENewPresentation:
OnNewPresentation(pEvent);
break;
default:
OnSessionEvent(pEvent, meType);
break;
}
}
// Release all resources held by this object.
void Player::Shutdown()
{
// Close the session
CloseSession();
// Shutdown the Media Foundation platform
MFShutdown();
if (m_hCloseEvent)
{
CloseHandle(m_hCloseEvent);
m_hCloseEvent = NULL;
}
}
/// Protected methods
void Player::OnTopologyStatus(IMFMediaEventPtr pEvent)
{
UINT32 status;
THROW_IF_ERR(pEvent->GetUINT32(MF_EVENT_TOPOLOGY_STATUS, &status));
if (status == MF_TOPOSTATUS_READY)
{
safe_release(m_pVideoDisplay);
// Get the IMFVideoDisplayControl interface from EVR. This call is
// expected to fail if the media file does not have a video stream.
(void)MFGetService(m_pSession.Get(), MR_VIDEO_RENDER_SERVICE,
IID_PPV_ARGS(m_pVideoDisplay.GetAddressOf()));
StartPlayback();
}
}
// Handler for MEEndOfPresentation event.
void Player::OnPresentationEnded(IMFMediaEventPtr pEvent)
{
// The session puts itself into the stopped state automatically.
m_state = Stopped;
}
// Handler for MENewPresentation event.
//
// This event is sent if the media source has a new presentation, which
// requires a new topology.
void Player::OnNewPresentation(IMFMediaEventPtr pEvent)
{
IMFPresentationDescriptorPtr pPD;
IMFTopologyPtr pTopology;
// Get the presentation descriptor from the event.
THROW_IF_ERR(GetEventObject(pEvent.Get(), pPD.GetAddressOf()));
// Create a partial topology.
pTopology = CreatePlaybackTopology(m_pSource, pPD, m_hwndVideo);
// Set the topology on the media session.
THROW_IF_ERR(m_pSession->SetTopology(0, pTopology.Get()));
m_state = OpenPending;
}
// Create a new instance of the media session.
void Player::CreateSession()
{
// Close the old session, if any.
CloseSession();
assert(m_state == Closed);
// Create the media session.
THROW_IF_ERR(MFCreateMediaSession(NULL, &m_pSession));
// Start pulling events from the media session
THROW_IF_ERR(m_pSession->BeginGetEvent((IMFAsyncCallback*)this, NULL));
m_state = Ready;
}
// Close the media session.
void Player::CloseSession()
{
// The IMFMediaSession::Close method is asynchronous, but the
// Player::CloseSession method waits on the MESessionClosed event.
//
// MESessionClosed is guaranteed to be the last event that the
// media session fires.
m_pVideoDisplay.Reset();
// First close the media session.
if (m_pSession)
{
DWORD dwWaitResult = 0;
m_state = Closing;
THROW_IF_ERR(m_pSession->Close());
// Wait for the close operation to complete
dwWaitResult = WaitForSingleObject(m_hCloseEvent, 5000);
if (dwWaitResult == WAIT_TIMEOUT)
{
assert(FALSE);
}
// Now there will be no more events from this session.
}
if (m_pSource)
{
m_pSource->Shutdown();
}
// Shut down the media session. (Synchronous operation, no events.)
if (m_pSession)
{
m_pSession->Shutdown();
}
m_pSource.Reset();
m_pSession.Reset();
m_state = Closed;
}
// Start playback from the current position.
void Player::StartPlayback()
{
assert(m_pSession != NULL);
prop_variant varStart;
// PROPVARIANT varStart;
// PropVariantInit(&varStart);
THROW_IF_ERR(m_pSession->Start(&GUID_NULL, &varStart));
// 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;
}
// Start playback from paused or stopped.
void Player::Play()
{
if (m_state != Paused && m_state != Stopped)
{
THROW_IF_ERR(MF_E_INVALIDREQUEST);
}
if (m_pSession == NULL || m_pSource == NULL)
{
THROW_IF_ERR(E_UNEXPECTED);
}
StartPlayback();
}
// Create a media source from a URL.
IMFMediaSourcePtr CreateMediaSource(const std::wstring& sURL)
{
MF_OBJECT_TYPE ObjectType = MF_OBJECT_INVALID;
IMFSourceResolverPtr pSourceResolver;
Microsoft::WRL::ComPtr<IUnknown> pSource;
IMFMediaSourcePtr pSourceReturn;
// Create the source resolver.
THROW_IF_ERR(MFCreateSourceResolver(pSourceResolver.GetAddressOf()));
// Use the source resolver to create the media source.
// Note: For simplicity this sample uses the synchronous method 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.
THROW_IF_ERR(pSourceResolver->CreateObjectFromURL(
sURL.c_str(), // 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.
THROW_IF_ERR(pSource.As<IMFMediaSource>(&pSourceReturn));
return pSourceReturn;
}
// Create an activation object for a renderer, based on the stream media type.
IMFActivatePtr CreateMediaSinkActivate(
IMFStreamDescriptor* pSourceSD, // Pointer to the stream descriptor.
HWND hVideoWindow // Handle to the video clipping window.
)
{
IMFMediaTypeHandlerPtr pHandler;
IMFActivatePtr pActivate;
// Get the media type handler for the stream.
THROW_IF_ERR(pSourceSD->GetMediaTypeHandler(pHandler.GetAddressOf()));
// Get the major media type.
GUID guidMajorType;
THROW_IF_ERR(pHandler->GetMajorType(&guidMajorType));
// Create an IMFActivate object for the renderer, based on the media type.
if (MFMediaType_Audio == guidMajorType)
{
// Create the audio renderer.
THROW_IF_ERR(MFCreateAudioRendererActivate(pActivate.GetAddressOf()));
}
else if (MFMediaType_Video == guidMajorType)
{
// Create the video renderer.
THROW_IF_ERR(MFCreateVideoRendererActivate(hVideoWindow, pActivate.GetAddressOf()));
}
else
{
// Unknown stream type.
THROW_IF_ERR(E_FAIL);
// Optionally, you could deselect this stream instead of failing.
}
// Return IMFActivate pointer to caller.
return pActivate;
}
// Add a source node to a topology.
IMFTopologyNodePtr AddSourceNode(
IMFTopologyPtr pTopology, // Topology.
IMFMediaSourcePtr pSource, // Media source.
IMFPresentationDescriptorPtr pPD, // Presentation descriptor.
IMFStreamDescriptorPtr pSD) // Stream descriptor.
{
IMFTopologyNodePtr pNode;
// Create the node.
THROW_IF_ERR(MFCreateTopologyNode(MF_TOPOLOGY_SOURCESTREAM_NODE, pNode.GetAddressOf()));
// Set the attributes.
THROW_IF_ERR(pNode->SetUnknown(MF_TOPONODE_SOURCE, pSource.Get()));
THROW_IF_ERR(pNode->SetUnknown(MF_TOPONODE_PRESENTATION_DESCRIPTOR, pPD.Get()));
THROW_IF_ERR(pNode->SetUnknown(MF_TOPONODE_STREAM_DESCRIPTOR, pSD.Get()));
// Add the node to the topology.
THROW_IF_ERR(pTopology->AddNode(pNode.Get()));
// Return the pointer to the caller.
return pNode;
}
// Add an output node to a topology.
IMFTopologyNodePtr AddOutputNode(
IMFTopologyPtr pTopology, // Topology.
IMFActivatePtr pActivate, // Media sink activation object.
DWORD dwId // Identifier of the stream sink.
)
{
IMFTopologyNodePtr pNode;
// Create the node.
THROW_IF_ERR(MFCreateTopologyNode(MF_TOPOLOGY_OUTPUT_NODE, &pNode));
// Set the object pointer.
THROW_IF_ERR(pNode->SetObject(pActivate.Get()));
// Set the stream sink ID attribute.
THROW_IF_ERR(pNode->SetUINT32(MF_TOPONODE_STREAMID, dwId));
THROW_IF_ERR(pNode->SetUINT32(MF_TOPONODE_NOSHUTDOWN_ON_REMOVE, FALSE));
// Add the node to the topology.
THROW_IF_ERR(pTopology->AddNode(pNode.Get()));
return pNode;
}
//</SnippetPlayer.cpp>
// Add a topology branch for one stream.
//
// For each stream, this function does the following:
//
// 1. Creates a source node associated with the stream.
// 2. Creates an output node for the renderer.
// 3. Connects the two nodes.
//
// The media session will add any decoders that are needed.
void AddBranchToPartialTopology(
IMFTopologyPtr pTopology, // Topology.
IMFMediaSourcePtr pSource, // Media source.
IMFPresentationDescriptorPtr pPD, // Presentation descriptor.
DWORD iStream, // Stream index.
HWND hVideoWnd) // Window for video playback.
{
IMFStreamDescriptorPtr pSD;
IMFActivatePtr pSinkActivate;
IMFTopologyNodePtr pSourceNode;
IMFTopologyNodePtr pOutputNode;
BOOL fSelected = FALSE;
THROW_IF_ERR(pPD->GetStreamDescriptorByIndex(iStream, &fSelected, &pSD));
if (fSelected)
{
// Create the media sink activation object.
pSinkActivate = CreateMediaSinkActivate(pSD.Get(), hVideoWnd);
// Add a source node for this stream.
pSourceNode = AddSourceNode(pTopology, pSource, pPD, pSD);
// Create the output node for the renderer.
pOutputNode = AddOutputNode(pTopology, pSinkActivate, 0);
// Connect the source node to the output node.
THROW_IF_ERR(pSourceNode->ConnectOutput(0, pOutputNode.Get(), 0));
}
// else: If not selected, don't add the branch.
}
// Create a playback topology from a media source.
// Receives a pointer to the topology.
IMFTopologyPtr CreatePlaybackTopology(
IMFMediaSourcePtr pSource, // Media source.
IMFPresentationDescriptorPtr pPD, // Presentation descriptor.
HWND hVideoWnd // Video window.
)
{
IMFTopologyPtr pTopology;
DWORD cSourceStreams = 0;
// Create a new topology.
THROW_IF_ERR(MFCreateTopology(&pTopology));
// Get the number of streams in the media source.
THROW_IF_ERR(pPD->GetStreamDescriptorCount(&cSourceStreams));
// For each stream, create the topology nodes and add them to the topology.
for (DWORD i = 0; i < cSourceStreams; i++)
{
AddBranchToPartialTopology(pTopology, pSource, pPD, i, hVideoWnd);
}
// Return the IMFTopology pointer to the caller.
return pTopology;
}
}