Media Foundation .NET

導入

Media Foundation .NET download | SourceForge.netからダウンロードできます。

ライセンスはLGPLで、v3.0以降はBSDも追加されています。Media Foundation .NET

使用法については、Media Foundation .NET - Browse /mfnet samples at SourceForge.netにサンプルがあります。これはWindows SDK for Windows VistaのC:\Program Files\Microsoft SDKs\Windows\v6.0\Samples\Multimedia\MediaFoundationに含まれるC++のコードを基に実装したものです。

たとえばMFPCreateMediaPlayer()は、次のように使用できます。

string pwszURL = "C:\\sample.wmv";
IntPtr hWnd = this.Handle;
IMFPMediaPlayer ppMediaPlayer = null;

HResult hr = MFExtern.MFPCreateMediaPlayer(pwszURL, true, 0, null, hWnd, out ppMediaPlayer);

オブジェクトの解放はCOMBase.SafeRelease(object)を呼びます。このメソッドはそのオブジェクトによって内部でMarshal.ReleaseComObject()かIDisposable.Dispose()を呼ぶため、オブジェクトの型を問わず呼び出せます。

Media Foundationからの移行

player.cpp

公式のサンプルにあるplayer.cppをMedia Foundation .NETを用いるようにすると、次のように書き換えられます。

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;

using MediaFoundation;
using MediaFoundation.EVR;
using MediaFoundation.Misc;


class CPlayer : COMBase, IMFAsyncCallback
{
    [DllImport("user32", CharSet = CharSet.Auto)]
    private extern static int PostMessage(IntPtr handle, int msg, IntPtr wParam, IntPtr lParam);

    protected const int WM_APP = 0x8000;
    public const int WM_APP_PLAYER_EVENT = WM_APP + 1;

    protected const int MF_SDK_VERSION = 0x0001;
    protected const int MF_API_VERSION = 0x0070;
    protected const int MF_VERSION = (MF_SDK_VERSION << 16 | MF_API_VERSION);

    public enum PlayerState
    {
        Closed = 0,
        Ready,
        OpenPending,
        Started,
        Paused,
        Stopped,
        Closing
    };

    protected IMFMediaSession m_pMediaSession = null;
    protected IMFMediaSource m_pMediaSource = null;
    protected IMFVideoDisplayControl m_pVideoDisplayControl = null;

    protected IntPtr m_hWndVideo = IntPtr.Zero;
    protected IntPtr m_hWndEvent = IntPtr.Zero;
    protected PlayerState m_playerState = PlayerState.Closed;
    protected AutoResetEvent m_hCloseEvent = null;


    protected HResult GetEventObject<Q>(IMFMediaEvent pMediaEvent, out Q ppObject)
    {
        ppObject = default(Q);

        PropVariant var = new PropVariant();
        HResult hr = pMediaEvent.GetValue(var);
        if (Succeeded(hr))
        {
            if (var.GetVariantType() == PropVariant.VariantType.IUnknown)
            {
                if (var.GetIUnknown() is Q)
                {
                    ppObject = (Q)var.GetIUnknown();
                    hr = HResult.S_OK;
                }
                else
                {
                    hr = HResult.E_NOINTERFACE;
                }
            }
            else
            {
                hr = HResult.MF_E_INVALIDTYPE;
            }
            // var.Clear(); // PropVariantはDispose()でClear()を呼ぶため、明示的な呼び出しは不要
        }
        return hr;
    }

    public static HResult CreateInstance(IntPtr hWndVideo, IntPtr hWndEvent, out CPlayer ppPlayer)
    {
        ppPlayer = null;

        CPlayer pPlayer = new CPlayer(hWndVideo, hWndEvent);
        if (pPlayer == null)
        {
            return HResult.E_OUTOFMEMORY;
        }

        HResult hr = pPlayer.Initialize();
        if (Succeeded(hr))
        {
            ppPlayer = pPlayer;
        }
        return hr;
    }

    protected CPlayer(IntPtr hWndVideo, IntPtr hWndEvent)
    {
        Debug.Assert(hWndVideo != IntPtr.Zero);
        Debug.Assert(hWndEvent != IntPtr.Zero);

        m_hWndVideo = hWndVideo;
        m_hWndEvent = hWndEvent;
    }

    ~CPlayer()
    {
        Debug.Assert(m_pMediaSession == null);
        Shutdown();
    }


    protected HResult Initialize()
    {
        HResult hr = MFExtern.MFStartup(MF_VERSION, MFStartup.Full);
        if (Succeeded(hr))
        {
            m_hCloseEvent = new AutoResetEvent(false);
        }
        return hr;
    }

    public HResult OpenURL(string sourceURL)
    {
        IMFPresentationDescriptor pSourcePD = null;
        IMFTopology pTopology = null;

        HResult hr = CreateSession();
        if (Failed(hr)) goto done;

        hr = CreateMediaSource(sourceURL, out m_pMediaSource);
        if (Failed(hr)) goto done;

        hr = m_pMediaSource.CreatePresentationDescriptor(out pSourcePD);
        if (Failed(hr)) goto done;

        hr = CreatePlaybackTopology(m_pMediaSource, pSourcePD, m_hWndVideo, out pTopology);
        if (Failed(hr)) goto done;

        hr = m_pMediaSession.SetTopology(0, pTopology);
        if (Failed(hr)) goto done;

        m_playerState = PlayerState.OpenPending;

        done:
        if (Failed(hr))
        {
            m_playerState = PlayerState.Closed;
        }

        SafeRelease(pSourcePD);
        SafeRelease(pTopology);
        return hr;
    }

    public HResult Pause()
    {
        if (m_playerState != PlayerState.Started)
        {
            return HResult.MF_E_INVALIDREQUEST;
        }
        if (m_pMediaSession == null || m_pMediaSource == null)
        {
            return HResult.E_UNEXPECTED;
        }

        HResult hr = m_pMediaSession.Pause();
        if (Succeeded(hr))
        {
            m_playerState = PlayerState.Paused;
        }
        return hr;
    }

    public HResult Stop()
    {
        if (m_playerState != PlayerState.Started && m_playerState != PlayerState.Paused)
        {
            return HResult.MF_E_INVALIDREQUEST;
        }
        if (m_pMediaSession == null)
        {
            return HResult.E_UNEXPECTED;
        }

        HResult hr = m_pMediaSession.Stop();
        if (Succeeded(hr))
        {
            m_playerState = PlayerState.Stopped;
        }
        return hr;
    }

    public HResult Shutdown()
    {
        HResult hr = CloseSession();

        MFExtern.MFShutdown();
        if (m_hCloseEvent != null)
        {
            m_hCloseEvent.Close();
            m_hCloseEvent = null;
        }

        return hr;
    }

    public PlayerState GetSate()
    {
        return m_playerState;
    }


    public HResult Repaint()
    {
        if (m_pVideoDisplayControl != null)
        {
            return m_pVideoDisplayControl.RepaintVideo();
        }
        else
        {
            return HResult.S_OK;
        }
    }

    public HResult ResizeVideo(int width, int height)
    {
        if (m_pVideoDisplayControl != null)
        {
            MFRect rcDest = new MFRect(0, 0, width, height);
            return m_pVideoDisplayControl.SetVideoPosition(null, rcDest);
        }
        else
        {
            return HResult.S_OK;
        }
    }

    public bool HasVideo()
    {
        return m_pVideoDisplayControl != null;
    }

    HResult IMFAsyncCallback.GetParameters(out MFASync pdwFlags, out MFAsyncCallbackQueue pdwQueue)
    {
        pdwFlags = MFASync.FastIOProcessingCallback;
        pdwQueue = MFAsyncCallbackQueue.Standard;

        return HResult.S_OK;
    }

    HResult IMFAsyncCallback.Invoke(IMFAsyncResult pAsyncResult)
    {
        MediaEventType mediaEventType = MediaEventType.MEUnknown;
        IMFMediaEvent pMediaEvent = null;

        HResult hr = HResult.S_OK;
        hr = m_pMediaSession.EndGetEvent(pAsyncResult, out pMediaEvent);
        if (Failed(hr)) goto done;

        hr = pMediaEvent.GetType(out mediaEventType);
        if (Failed(hr)) goto done;

        if (mediaEventType == MediaEventType.MESessionClosed)
        {
            m_hCloseEvent.Set();
        }
        else
        {
            hr = m_pMediaSession.BeginGetEvent(this, null);
            if (Failed(hr)) goto done;
        }

        if (m_playerState != PlayerState.Closing)
        {
            unsafe
            {
                IntPtr wParam = (IntPtr)GCHandle.Alloc(pMediaEvent);
                IntPtr lParam = new IntPtr((int)mediaEventType);
                PostMessage(m_hWndEvent, WM_APP_PLAYER_EVENT, wParam, lParam);
            }
            // イベントで処理すれば、unsafeを用いる必要はない
        }

        done:
        if (Failed(hr)) SafeRelease(pMediaEvent);
        return HResult.S_OK;
    }

    public HResult HandleEvent(IntPtr pMediaEventPtr)
    {
        IMFMediaEvent pMediaEvent = (IMFMediaEvent)GCHandle.FromIntPtr((IntPtr)pMediaEventPtr).Target;
        if (pMediaEvent == null)
        {
            return HResult.E_POINTER;
        }

        MediaEventType mediaEventType = MediaEventType.MEUnknown;
        HResult hr = pMediaEvent.GetType(out mediaEventType);
        if (Failed(hr)) goto done;

        HResult hrStatus = HResult.S_OK;
        hr = pMediaEvent.GetStatus(out hrStatus);

        if (Succeeded(hr) && Failed(hrStatus))
        {
            hr = hrStatus;
        }
        if (Failed(hr)) goto done;

        switch (mediaEventType)
        {
            case MediaEventType.MESessionTopologyStatus:
                hr = OnTopologyStatus(pMediaEvent);
                break;

            case MediaEventType.MEEndOfPresentation:
                hr = OnPresentationEnded(pMediaEvent);
                break;

            case MediaEventType.MENewPresentation:
                hr = OnNewPresentation(pMediaEvent);
                break;

            default:
                hr = OnSessionEvent(pMediaEvent, mediaEventType);
                break;
        }

        done:
        SafeRelease(pMediaEvent);
        return hr;
    }


    protected HResult OnTopologyStatus(IMFMediaEvent pMediaEvent)
    {
        int status;

        HResult hr = pMediaEvent.GetUINT32(MFAttributesClsid.MF_EVENT_TOPOLOGY_STATUS, out status);
        if (Succeeded(hr) && (MFTopoStatus)status == MFTopoStatus.Ready)
        {
            SafeRelease(m_pVideoDisplayControl);
            m_pVideoDisplayControl = null;

            object obj;
            hr = MFExtern.MFGetService(
                m_pMediaSession,
                MFServices.MR_VIDEO_RENDER_SERVICE,
                typeof(IMFVideoDisplayControl).GUID,
                out obj
                );
            m_pVideoDisplayControl = obj as IMFVideoDisplayControl;

            if (Succeeded(hr))
            {
                hr = StartPlayback();
            }
        }
        return hr;
    }

    protected HResult OnPresentationEnded(IMFMediaEvent pMediaEvent)
    {
        m_playerState = PlayerState.Stopped;
        return HResult.S_OK;
    }

    protected HResult OnNewPresentation(IMFMediaEvent pMediaEvent)
    {
        IMFPresentationDescriptor pPD = null;
        IMFTopology pTopology = null;

        HResult hr = GetEventObject(pMediaEvent, out pPD);
        if (Failed(hr)) goto done;

        hr = CreatePlaybackTopology(m_pMediaSource, pPD, m_hWndVideo, out pTopology);
        if (Failed(hr)) goto done;

        hr = m_pMediaSession.SetTopology(0, pTopology);
        if (Failed(hr)) goto done;

        m_playerState = PlayerState.OpenPending;

        done:
        SafeRelease(pTopology);
        SafeRelease(pPD);
        return HResult.S_OK;
    }


    protected HResult OnSessionEvent(IMFMediaEvent pMediaEvent, MediaEventType mediaEventType)
    {
        switch (mediaEventType)
        {
            case MediaEventType.MESessionStarted:
                m_playerState = PlayerState.Started;
                break;

            case MediaEventType.MESessionPaused:
                m_playerState = PlayerState.Paused;
                break;
        }
        return HResult.S_OK;
    }


    protected HResult CreateSession()
    {
        HResult hr = CloseSession();
        if (Failed(hr)) goto done;

        Debug.Assert(m_playerState == PlayerState.Closed);

        hr = MFExtern.MFCreateMediaSession(null, out m_pMediaSession);
        if (Failed(hr)) goto done;

        hr = m_pMediaSession.BeginGetEvent(this, null);
        if (Failed(hr)) goto done;

        m_playerState = PlayerState.Ready;

        done:
        return hr;
    }

    protected HResult CloseSession()
    {
        HResult hr = HResult.S_OK;

        SafeRelease(m_pVideoDisplayControl);
        m_pVideoDisplayControl = null;

        if (m_pMediaSession != null)
        {
            m_playerState = PlayerState.Closing;

            hr = m_pMediaSession.Close();
            if (Succeeded(hr))
            {
                const int timeoutMS = 5000;
                bool result = m_hCloseEvent.WaitOne(timeoutMS, true);
                if (!result)
                {
                    Debug.Fail("WaitForSingleObject timed out!");
                }
            }
        }

        if (Succeeded(hr))
        {
            if (m_pMediaSource != null)
            {
                hr = m_pMediaSource.Shutdown();
            }

            if (m_pMediaSession != null)
            {
                hr = m_pMediaSession.Shutdown();
            }
        }

        SafeRelease(m_pMediaSource);
        m_pMediaSource = null;
        SafeRelease(m_pMediaSession);
        m_pMediaSession = null;

        m_playerState = PlayerState.Closed;
        return hr;
    }


    protected HResult StartPlayback()
    {
        Debug.Assert(m_pMediaSession != null);

        PropVariant varStart = new PropVariant();

        HResult hr = m_pMediaSession.Start(Guid.Empty, varStart);
        if (Succeeded(hr))
        {
            m_playerState = PlayerState.Started;
        }

        // varStart.Clear(); // PropVariantはDispose()でClear()を呼ぶため、明示的な呼び出しは不要
        return hr;
    }

    public HResult Play()
    {
        if (m_playerState != PlayerState.Paused && m_playerState != PlayerState.Stopped)
        {
            return HResult.MF_E_INVALIDREQUEST;
        }
        if (m_pMediaSession == null || m_pMediaSource == null)
        {
            return HResult.E_UNEXPECTED;
        }
        return StartPlayback();
    }


    protected HResult CreateMediaSource(string sourceURL, out IMFMediaSource ppMediaSource)
    {
        IMFSourceResolver pSourceResolver = null;
        object pMediaSource = null;
        ppMediaSource = null;

        HResult hr = MFExtern.MFCreateSourceResolver(out pSourceResolver);
        if (Failed(hr)) goto done;

        MFObjectType ObjectType = MFObjectType.Invalid;
        hr = pSourceResolver.CreateObjectFromURL(
            sourceURL,
            MFResolution.MediaSource,
            null,
            out ObjectType,
            out pMediaSource
        );
        if (Failed(hr)) goto done;

        ppMediaSource = pMediaSource as IMFMediaSource;
        if (ppMediaSource == null)
        {
            hr = HResult.E_NOINTERFACE;
        }

        done:
        SafeRelease(pSourceResolver);
        if (ppMediaSource == null) SafeRelease(pMediaSource);
        return hr;
    }


    protected HResult CreatePlaybackTopology(IMFMediaSource pMediaSource, IMFPresentationDescriptor pPD, IntPtr hVideoWnd, out IMFTopology ppTopology)
    {
        IMFTopology pTopology = null;
        ppTopology = null;

        HResult hr = MFExtern.MFCreateTopology(out pTopology);
        if (Failed(hr)) goto done;

        int dwSourceStreamsCount = 0;
        hr = pPD.GetStreamDescriptorCount(out dwSourceStreamsCount);
        if (Failed(hr)) goto done;

        for (int i = 0; i < dwSourceStreamsCount; i++)
        {
            hr = AddBranchToPartialTopology(pTopology, pMediaSource, pPD, i, hVideoWnd);
            if (Failed(hr)) goto done;
        }

        ppTopology = pTopology;

        done:
        if (ppTopology == null) SafeRelease(pTopology);
        return hr;
    }

    protected HResult AddBranchToPartialTopology(IMFTopology pTopology, IMFMediaSource pMediaSource, IMFPresentationDescriptor pPD, int dwStreamIndex, IntPtr hVideoWnd)
    {
        IMFStreamDescriptor pSD = null;
        IMFActivate pSinkActivate = null;
        IMFTopologyNode pSourceNode = null;
        IMFTopologyNode pOutputNode = null;

        bool fSelected = false;

        HResult hr = pPD.GetStreamDescriptorByIndex(dwStreamIndex, out fSelected, out pSD);
        if (Failed(hr)) goto done;

        if (fSelected)
        {
            hr = CreateMediaSinkActivate(pSD, hVideoWnd, out pSinkActivate);
            if (Failed(hr)) goto done;

            hr = AddSourceNode(pTopology, pMediaSource, pPD, pSD, out pSourceNode);
            if (Failed(hr)) goto done;

            hr = AddOutputNode(pTopology, pSinkActivate, 0, out pOutputNode);
            if (Failed(hr)) goto done;

            hr = pSourceNode.ConnectOutput(0, pOutputNode, 0);
        }

        done:
        SafeRelease(pSD);
        SafeRelease(pSinkActivate);
        SafeRelease(pSourceNode);
        SafeRelease(pOutputNode);
        return hr;
    }

    protected HResult CreateMediaSinkActivate(IMFStreamDescriptor pSourceSD, IntPtr hVideoClippingWindow, out IMFActivate ppActivate)
    {
        IMFMediaTypeHandler pMediaTypeHandler = null;
        IMFActivate pActivate = null;
        ppActivate = null;

        HResult hr = pSourceSD.GetMediaTypeHandler(out pMediaTypeHandler);
        if (Failed(hr)) goto done;

        Guid guidMajorType;
        hr = pMediaTypeHandler.GetMajorType(out guidMajorType);
        if (Failed(hr)) goto done;

        if (MFMediaType.Audio == guidMajorType)
        {
            hr = MFExtern.MFCreateAudioRendererActivate(out pActivate);
        }
        else if (MFMediaType.Video == guidMajorType)
        {
            hr = MFExtern.MFCreateVideoRendererActivate(hVideoClippingWindow, out pActivate);
        }
        else
        {
            hr = HResult.E_FAIL;
        }

        if (Failed(hr)) goto done;

        ppActivate = pActivate;

        done:
        SafeRelease(pMediaTypeHandler);
        if (ppActivate == null) SafeRelease(pActivate);
        return hr;
    }

    protected HResult AddSourceNode(IMFTopology pTopology, IMFMediaSource pMediaSource, IMFPresentationDescriptor pPD, IMFStreamDescriptor pSD, out IMFTopologyNode ppSourceNode)
    {
        IMFTopologyNode pNode = null;
        ppSourceNode = null;

        HResult hr = MFExtern.MFCreateTopologyNode(MFTopologyType.SourcestreamNode, out pNode);
        if (Failed(hr)) goto done;

        hr = pNode.SetUnknown(MFAttributesClsid.MF_TOPONODE_SOURCE, pMediaSource);
        if (Failed(hr)) goto done;

        hr = pNode.SetUnknown(MFAttributesClsid.MF_TOPONODE_PRESENTATION_DESCRIPTOR, pPD);
        if (Failed(hr)) goto done;

        hr = pNode.SetUnknown(MFAttributesClsid.MF_TOPONODE_STREAM_DESCRIPTOR, pSD);
        if (Failed(hr)) goto done;

        hr = pTopology.AddNode(pNode);
        if (Failed(hr)) goto done;

        ppSourceNode = pNode;

        done:
        if (ppSourceNode == null) SafeRelease(pNode);
        return hr;
    }

    protected HResult AddOutputNode(IMFTopology pTopology, IMFActivate pMediaSinkActivate, int dwStreamSinkId, out IMFTopologyNode ppOutputNode)
    {
        IMFTopologyNode pNode = null;
        ppOutputNode = null;

        HResult hr = MFExtern.MFCreateTopologyNode(MFTopologyType.OutputNode, out pNode);
        if (Failed(hr)) goto done;

        hr = pNode.SetObject(pMediaSinkActivate);
        if (Failed(hr)) goto done;

        hr = pNode.SetUINT32(MFAttributesClsid.MF_TOPONODE_STREAMID, dwStreamSinkId);
        if (Failed(hr)) goto done;

        hr = pNode.SetUINT32(MFAttributesClsid.MF_TOPONODE_NOSHUTDOWN_ON_REMOVE, 0);
        if (Failed(hr)) goto done;

        hr = pTopology.AddNode(pNode);
        if (Failed(hr)) goto done;

        ppOutputNode = pNode;

        done:
        if (ppOutputNode == null) SafeRelease(pNode);
        return hr;
    }
}

PlayerSeeking

PlayerSeekingは、次のように書き換えられます。

internal class PlayerSeeking : COMBase
{
    private enum Command
    {
        CmdNone = 0,
        CmdStop,
        CmdStart,
        CmdPause,
        CmdSeek,
    };

    // Describes the current or requested state, with respect to seeking and playback rate.
    private struct SeekState
    {
        public Command command;
        public float fRate; // Playback rate
        public bool bThin; // Thinned playback?
        public long hnsStart; // Start position
    };


    private int m_bPending; // Is a request pending?

    private SeekState m_state; // Current nominal state.
    private SeekState m_request; // Pending request.

    private object m_critsec = new object(); // Protects the seeking and rate-change states.

    private MFSessionCapabilities m_caps; // Session caps.
    private bool m_bCanScrub; // Does the current session support rate = 0.

    private long m_hnsDuration; // Duration of the current presentation.
    private float m_fPrevRate;

    private IMFMediaSession m_pSession;
    private IMFRateControl m_pRate;
    private IMFRateSupport m_pRateSupport;
    private IMFPresentationClock m_pClock;


    private const int CMD_PENDING = 0x01;
    private const int CMD_PENDING_SEEK = 0x02;
    private const int CMD_PENDING_RATE = 0x04;


    // Given a topology, returns a pointer to the presentation descriptor.
    private HResult GetPresentationDescriptorFromTopology(IMFTopology pTopology, out IMFPresentationDescriptor ppPD)
    {
        HResult hr = HResult.S_OK;
        ppPD = null;

        IMFCollection pCollection = null;
        object pUnk = null;
        IMFTopologyNode pNode = null;
        object pPD = null;

        // Get the collection of source nodes from the topology.
        hr = pTopology.GetSourceNodeCollection(out pCollection);
        if (Failed(hr)) goto done;

        // Any of the source nodes should have the PD, so take the first object in the collection.
        hr = pCollection.GetElement(0, out pUnk);
        if (Failed(hr)) goto done;

        pNode = pUnk as IMFTopologyNode;
        if (pNode == null)
        {
            hr = HResult.E_NOINTERFACE;
            goto done;
        }

        // Get the PD, which is stored as an attribute.
        hr = pNode.GetUnknown(MFAttributesClsid.MF_TOPONODE_PRESENTATION_DESCRIPTOR, typeof(IMFPresentationDescriptor).GUID, out pPD);
        if (Failed(hr)) goto done;

        ppPD = (IMFPresentationDescriptor)pPD;

        done:
        SafeRelease(pCollection);
        SafeRelease(pUnk);
        if (ppPD == null) SafeRelease(pPD);
        return hr;
    }


    public PlayerSeeking()
    {
        m_pClock = null;
        // m_pSession = null;
        m_pRate = null;
        m_pRateSupport = null;
        m_bPending = 0;

        Clear();
    }

    ~PlayerSeeking()
    {
        SafeRelease(m_pClock);
        // SafeRelease(m_pSession);
        SafeRelease(m_pRate);
        SafeRelease(m_pRateSupport);
    }

    // Clears any resources for the current topology.
    public HResult Clear()
    {
        m_caps = 0;
        m_bCanScrub = false;

        m_hnsDuration = 0;
        m_fPrevRate = 1.0f;

        m_bPending = 0;

        SafeRelease(m_pClock);
        // SafeRelease(m_pSession);
        SafeRelease(m_pRate);
        SafeRelease(m_pRateSupport);

        m_pClock = null;
        // m_pSession = null;
        m_pRate = null;
        m_pRateSupport = null;

        m_state.command = Command.CmdStop;
        m_state.fRate = 1.0f;
        m_state.bThin = false;
        m_state.hnsStart = 0L;

        m_request.command = Command.CmdNone;
        m_request.fRate = 1.0f;
        m_request.bThin = false;
        m_request.hnsStart = 0L;

        return HResult.S_OK;
    }


    // Call when the full playback topology is ready.
    public HResult SetTopology(IMFMediaSession pSession, IMFTopology pTopology)
    {
        HResult hr = HResult.S_OK;
        HResult hrTmp = HResult.S_OK; // For non-critical failures.

        IMFClock pClock = null;
        IMFPresentationDescriptor pPD = null;

        Clear();

        // Get the session capabilities.
        hr = pSession.GetSessionCapabilities(out m_caps);
        if (Failed(hr)) goto done;

        // Get the presentation descriptor from the topology.
        hr = GetPresentationDescriptorFromTopology(pTopology, out pPD);
        if (Failed(hr)) goto done;

        // Get the duration from the presentation descriptor (optional)
        hr = pPD.GetUINT64(MFAttributesClsid.MF_PD_DURATION, out m_hnsDuration);
        if (Failed(hr)) goto done;

        // Get the presentation clock (optional)
        hrTmp = pSession.GetClock(out pClock);
        if (Succeeded(hrTmp))
        {
            m_pClock = pClock as IMFPresentationClock;
            if (m_pClock == null)
            {
                hr = HResult.E_NOINTERFACE;
                goto done;
            }
        }

        // Get the rate control interface (optional)
        object rate;
        hrTmp = MFExtern.MFGetService(pSession, MFServices.MF_RATE_CONTROL_SERVICE, typeof(IMFRateControl).GUID, out rate);
        m_pRate = (IMFRateControl)rate;

        // Get the rate support interface (optional)
        if (Succeeded(hrTmp))
        {
            object rateSupport;
            hrTmp = MFExtern.MFGetService(pSession, MFServices.MF_RATE_CONTROL_SERVICE, typeof(IMFRateSupport).GUID, out rateSupport);
            m_pRateSupport = (IMFRateSupport)rateSupport;
        }
        if (Succeeded(hrTmp))
        {
            // Check if rate 0 (scrubbing) is supported.
            hrTmp = m_pRateSupport.IsRateSupported(true, 0, null);
        }
        if (Succeeded(hrTmp))
        {
            m_bCanScrub = true;
        }

        // if m_pRate is null, m_bCanScrub must be false.
        Debug.Assert(m_pRate != null || !m_bCanScrub);

        // Cache a pointer to the session.
        m_pSession = pSession;

        done:
        SafeRelease(pPD);
        if (m_pClock == null) SafeRelease(pClock);
        return hr;
    }

    // Call when media session fires an event.
    public HResult SessionEvent(MediaEventType type, HResult hrStatus, IMFMediaEvent pEvent)
    {
        PropVariant var;
        HResult hr = HResult.S_OK;

        switch (type)
        {
            case MediaEventType.MESessionStarted:
                OnSessionStart(hrStatus);
                break;

            case MediaEventType.MESessionStopped:
                OnSessionStop(hrStatus);
                break;

            case MediaEventType.MESessionPaused:
                OnSessionPause(hrStatus);
                break;

            case MediaEventType.MESessionRateChanged:
                // If the rate change succeeded, we've already got the rate cached. If it failed, try to get the actual rate.
                if (Failed(hrStatus))
                {
                    var = new PropVariant();

                    hr = pEvent.GetValue(var);

                    if (Succeeded(hr) && (var.GetType() == typeof(float)))
                    {
                        m_state.fRate = var.GetFloat();
                    }
                }
                break;

            case MediaEventType.MESessionEnded:
                OnSessionEnded(hrStatus);
                break;

            case MediaEventType.MESessionCapabilitiesChanged:
                // The session capabilities changed. Get the updated capabilities.
                m_caps = (MFSessionCapabilities)MFExtern.MFGetAttributeUINT32(pEvent, MFAttributesClsid.MF_EVENT_SESSIONCAPS, (int)m_caps);
                break;
        }
        return HResult.S_OK;
    }


    // Starts playback.
    public HResult Start()
    {
        HResult hr = HResult.S_OK;

        lock (m_critsec)
        {
            // If another operation is pending, cache the request.
            // Otherwise, start the media session.
            if (m_bPending != 0)
            {
                m_request.command = Command.CmdStart;
            }
            else
            {
                PropVariant varStart;
                varStart = new PropVariant();

                hr = m_pSession.Start(Guid.Empty, varStart);

                m_state.command = Command.CmdStart;
                m_bPending = CMD_PENDING;
            }
        }
        return hr;
    }

    // Pauses playback.
    public HResult Pause()
    {
        HResult hr = HResult.S_OK;

        lock (m_critsec)
        {
            // If another operation is pending, cache the request.
            // Otherwise, pause the media session.
            if (m_bPending != 0)
            {
                m_request.command = Command.CmdPause;
            }
            else
            {
                hr = m_pSession.Pause();

                m_state.command = Command.CmdPause;
                m_bPending = CMD_PENDING;
            }
        }
        return hr;
    }

    // Stops playback.
    public HResult Stop()
    {
        HResult hr = HResult.S_OK;

        lock (m_critsec)
        {
            // If another operation is pending, cache the request.
            // Otherwise, stop the media session.
            if (m_bPending != 0)
            {
                m_request.command = Command.CmdStop;
            }
            else
            {
                hr = m_pSession.Stop();

                m_state.command = Command.CmdStop;
                m_bPending = CMD_PENDING;
            }
        }
        return hr;
    }


    // Queries whether the current session supports seeking.
    public HResult CanSeek(out bool pbCanSeek)
    {
        // Note: The MFSESSIONCAP_SEEK flag is sufficient for seeking.
        // However, to implement a seek bar, an application also needs the duration (to get
        // the valid range) and a presentation clock (to get the current position).

        pbCanSeek = (
            ((m_caps & MFSessionCapabilities.Seek) == MFSessionCapabilities.Seek) &&
            (m_hnsDuration > 0) &&
            (m_pClock != null)
            );

        return HResult.S_OK;
    }

    // Gets the duration of the current presentation.
    public HResult GetDuration(out long phnsDuration)
    {
        phnsDuration = m_hnsDuration;

        if (m_hnsDuration == 0)
        {
            return HResult.MF_E_NO_DURATION;
        }
        else
        {
            return HResult.S_OK;
        }
    }

    // Gets the current playback position.
    public HResult GetCurrentPosition(out long phnsPosition)
    {
        phnsPosition = 0L;
        HResult hr = HResult.S_OK;

        lock (m_critsec)
        {
            if (m_pClock == null)
            {
                return HResult.MF_E_NO_CLOCK;
            }

            // Return, in order:
            // 1. Cached seek request (nominal position).
            // 2. Pending seek operation (nominal position).
            // 3. Presentation time (actual position).

            if (m_request.command == Command.CmdSeek)
            {
                phnsPosition = m_request.hnsStart;
            }
            else if ((m_bPending & CMD_PENDING_SEEK) != 0)
            {
                phnsPosition = m_state.hnsStart;
            }
            else
            {
                hr = m_pClock.GetTime(out phnsPosition);
            }
        }
        return hr;
    }

    // Sets the current playback position.
    public HResult SetPosition(long hnsPosition)
    {
        HResult hr = HResult.S_OK;

        lock (m_critsec)
        {
            if (m_bPending != 0)
            {
                // Currently seeking or changing rates, so cache this request.
                m_request.command = Command.CmdSeek;
                m_request.hnsStart = hnsPosition;
            }
            else
            {
                hr = SetPositionInternal(hnsPosition);
            }
        }
        return hr;
    }

    // Queries whether the current session supports scrubbing.
    public HResult CanScrub(out bool pbCanScrub)
    {
        pbCanScrub = m_bCanScrub;
        return HResult.S_OK;
    }

    // Enables or disables scrubbing.
    public HResult Scrub(bool bScrub)
    {
        HResult hr = HResult.S_OK;

        // Scrubbing is implemented as rate = 0.
        lock (m_critsec)
        {
            if (m_pRate == null)
            {
                return HResult.MF_E_INVALIDREQUEST;
            }
            if (!m_bCanScrub)
            {
                return HResult.MF_E_INVALIDREQUEST;
            }

            if (bScrub)
            {
                // Enter scrubbing mode. Cache the current rate.
                if (GetNominalRate() != 0)
                {
                    m_fPrevRate = m_state.fRate;
                }

                hr = SetRate(0.0f);
            }
            else
            {
                // Leaving scrubbing mode. Restore the old rate.
                if (GetNominalRate() == 0)
                {
                    hr = SetRate(m_fPrevRate);
                }
            }
        }
        return hr;
    }

    // Queries whether the current session supports fast-forward.
    public HResult CanFastForward(out bool pbCanFF)
    {
        pbCanFF = ((m_caps & MFSessionCapabilities.RateForward) == MFSessionCapabilities.RateForward);
        return HResult.S_OK;
    }

    // Queries whether the current session supports rewind (reverse play).
    public HResult CanRewind(out bool pbCanRewind)
    {
        pbCanRewind = ((m_caps & MFSessionCapabilities.RateReverse) == MFSessionCapabilities.RateReverse);
        return HResult.S_OK;
    }

    // Switches to fast-forward playback, as follows:
    // - If the current rate is < 0 (reverse play), switch to 1x speed.
    // - Otherwise, double the current playback rate.
    //
    // Note: This method is for convenience; the application can also call SetRate.
    public HResult FastForward()
    {
        if (m_pRate == null)
        {
            return HResult.MF_E_INVALIDREQUEST;
        }

        HResult hr = HResult.S_OK;
        float fTarget = GetNominalRate() * 2;

        if (fTarget <= 0.0f)
        {
            fTarget = 1.0f;
        }

        hr = SetRate(fTarget);
        return hr;
    }

    // Switches to reverse playback, as follows:
    // - If the current rate is > 0 (forward playback), switch to -1x speed.
    // - Otherwise, double the current (reverse) playback rate.
    //
    // Note: This method is for convenience; the application can also call SetRate.
    public HResult Rewind()
    {
        if (m_pRate == null)
        {
            return HResult.MF_E_INVALIDREQUEST;
        }

        HResult hr = HResult.S_OK;
        float fTarget = GetNominalRate() * 2;

        if (fTarget >= 0.0f)
        {
            fTarget = -1.0f;
        }

        hr = SetRate(fTarget);
        return hr;
    }


    // Sets the playback rate.
    public HResult SetRate(float fRate)
    {
        HResult hr = HResult.S_OK;
        bool bThin = false;

        lock (m_critsec)
        {
            if (fRate == GetNominalRate())
            {
                return HResult.S_OK; // no-op
            }

            if (m_pRateSupport == null)
            {
                return HResult.MF_E_INVALIDREQUEST;
            }

            // Check if this rate is supported. Try non-thinned playback first, then fall back to thinned playback.
            hr = m_pRateSupport.IsRateSupported(false, fRate, null);

            if (Failed(hr))
            {
                bThin = true;
                hr = m_pRateSupport.IsRateSupported(true, fRate, null);
            }

            if (Failed(hr))
            {
                // Unsupported rate.
                return hr;
            }

            // If there is an operation pending, cache the request.
            if (m_bPending != 0)
            {
                m_request.fRate = fRate;
                m_request.bThin = bThin;

                // Remember the current transport state (play, paused, etc), so that we
                // can restore it after the rate change, if necessary. However, if
                // anothercommand is already pending, that one takes precedent.

                if (m_request.command == Command.CmdNone)
                {
                    m_request.command = m_state.command;
                }

            }
            else
            {
                // No pending operation. Commit the new rate.
                hr = CommitRateChange(fRate, bThin);
            }
        }
        return hr;
    }

    // Sets the playback position.
    private HResult SetPositionInternal(long hnsPosition)
    {
        Debug.Assert(m_bPending == 0);

        if (m_pSession == null)
        {
            return HResult.MF_E_INVALIDREQUEST;
        }

        HResult hr = HResult.S_OK;

        PropVariant varStart = new PropVariant(hnsPosition);
        hr = m_pSession.Start(Guid.Empty, varStart);

        if (Succeeded(hr))
        {
            // Store the pending state.
            m_state.command = Command.CmdStart;
            m_state.hnsStart = hnsPosition;
            m_bPending = CMD_PENDING_SEEK;
        }
        return hr;
    }

    // Sets the playback rate.
    private HResult CommitRateChange(float fRate, bool bThin)
    {
        Debug.Assert(m_bPending == 0);

        // Caller holds the lock.
        HResult hr = HResult.S_OK;
        long hnsSystemTime = 0;
        long hnsClockTime = 0;

        Command cmdNow = m_state.command;

        IMFClock pClock = null;

        // Allowed rate transitions:

        // Positive <-> negative: Stopped
        // Negative <-> zero: Stopped
        // Postive <-> zero: Paused or stopped

        if ((fRate > 0 && m_state.fRate <= 0) || (fRate < 0 && m_state.fRate >= 0))
        {
            // Transition to stopped.
            if (cmdNow == Command.CmdStart)
            {
                // Get the current clock position. This will be the restart time.
                hr = m_pSession.GetClock(out pClock);
                if (Failed(hr)) goto done;

                hr = pClock.GetCorrelatedTime(0, out hnsClockTime, out hnsSystemTime);
                if (Failed(hr)) goto done;

                Debug.Assert(hnsSystemTime != 0);

                // Stop and set the rate
                hr = Stop();
                if (Failed(hr)) goto done;

                // Cache Request: Restart from stop.
                m_request.command = Command.CmdSeek;
                m_request.hnsStart = hnsClockTime;
            }
            else if (cmdNow == Command.CmdPause)
            {
                // The current state is paused.

                // For this rate change, the session must be stopped.
                // However, the session cannot transition back from stopped to paused.
                // Therefore, this rate transition is not supported while paused.

                hr = HResult.MF_E_UNSUPPORTED_STATE_TRANSITION;
                goto done;
            }
        }
        else if (fRate == 0 && m_state.fRate != 0)
        {
            if (cmdNow != Command.CmdPause)
            {
                // Transition to paused.

                // This transisition requires the paused state.

                // Pause and set the rate.
                hr = Pause();
                if (Failed(hr)) goto done;

                // Request: Switch back to current state.
                m_request.command = cmdNow;
            }
        }

        // Set the rate.
        hr = m_pRate.SetRate(bThin, fRate);
        if (Failed(hr)) goto done;

        // Adjust our current rate and requested rate.
        m_request.fRate = m_state.fRate = fRate;

        done:
        SafeRelease(pClock);
        return hr;
    }

    private float GetNominalRate()
    {
        return m_request.fRate;
    }


    // Called when playback starts or restarts.
    private HResult OnSessionStart(HResult hrStatus)
    {
        HResult hr = HResult.S_OK;

        if (Failed(hrStatus))
        {
            return hrStatus;
        }

        // The Media Session completed a start/seek operation.
        // Check if there is another seek request pending.

        UpdatePendingCommands(Command.CmdStart);

        return hr;
    }

    // Called when playback stops.
    private HResult OnSessionStop(HResult hrStatus)
    {
        HResult hr = HResult.S_OK;

        if (Failed(hrStatus))
        {
            return hrStatus;
        }

        // The Media Session completed a transition to stopped. This might occur
        // because we are changing playback direction (forward/rewind).
        // Check if there is a pending rate-change request.

        UpdatePendingCommands(Command.CmdStop);

        return hr;
    }

    // Called when playback pauses.
    private HResult OnSessionPause(HResult hrStatus)
    {
        HResult hr = HResult.S_OK;

        if (Failed(hrStatus))
        {
            return hrStatus;
        }

        hr = UpdatePendingCommands(Command.CmdPause);

        return hr;
    }

    // Called when the session ends.
    private HResult OnSessionEnded(HResult hr)
    {
        // After the session ends, playback starts from position zero. But if the
        // current playback rate is reversed, playback would end immediately
        // (reversing from position 0). Therefore, reset the rate to 1x.

        if (GetNominalRate() < 0.0f)
        {
            m_state.command = Command.CmdStop;

            hr = CommitRateChange(1.0f, false);
        }

        return hr;
    }

    // Called after an operation completes.
    // This method executes any cached requests.
    private HResult UpdatePendingCommands(Command cmd)
    {
        HResult hr = HResult.S_OK;

        lock (m_critsec)
        {
            if (m_bPending != 0 && m_state.command == cmd)
            {
                m_bPending = 0;

                // The current pending command has completed.

                // First look for rate changes.
                if (m_request.fRate != m_state.fRate)
                {
                    hr = CommitRateChange(m_request.fRate, m_request.bThin);
                    if (Failed(hr)) goto done;
                }

                // Now look for seek requests.
                if (m_bPending == 0)
                {
                    switch (m_request.command)
                    {
                        case Command.CmdNone:
                            // Nothing to do.
                            break;

                        case Command.CmdStart:
                            Start();
                            break;

                        case Command.CmdPause:
                            Pause();
                            break;

                        case Command.CmdStop:
                            Stop();
                            break;

                        case Command.CmdSeek:
                            SetPositionInternal(m_request.hnsStart);
                            break;
                    }
                    m_request.command = Command.CmdNone;
                }
            }
        }
        done:
        return hr;
    }
}

トラブル対処法

MediaFoundationのメンバにアクセスしたとき、「型 'System.__ComObject' の COM オブジェクトをインターフェイス型 'MediaFoundation.***' にキャストできません。IID '{***}' が指定されたインターフェイスの COM コンポーネント上での QueryInterface 呼び出しのときに次のエラーが発生したため、この操作に失敗しました: インターフェイスがサポートされていません (HRESULT からの例外:0x80004002 (E_NOINTERFACE))。」としてSystem.InvalidCastException例外が発生するときには、その呼び出し場所のスレッドと、そのインスタンスを作成したスレッドが一致しているか確認します。

Microsoft Learnから検索