/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Mobility Components.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "mfvideorenderercontrol.h"
#include <mferror.h>
#include <qabstractvideosurface.h>
#include <qvideosurfaceformat.h>
#include <qtcore/qtimer.h>
#include <qtcore/qmutex.h>
#include <qtcore/qcoreevent.h>
#include <qtcore/qcoreapplication.h>
#include <qtcore/qthread.h>
#include "guiddef.h"
#include <qtcore/qdebug.h>

//#define DEBUG_MEDIAFOUNDATION
#define PAD_TO_DWORD(x)  (((x) + 3) & ~3)

namespace
{
    class MediaSampleVideoBuffer : public QAbstractVideoBuffer
    {
    public:
        MediaSampleVideoBuffer(IMFMediaBuffer *buffer, int bytesPerLine)
            : QAbstractVideoBuffer(NoHandle)
            , m_buffer(buffer)
            , m_bytesPerLine(bytesPerLine)
            , m_mapMode(NotMapped)
        {
            buffer->AddRef();
        }

        ~MediaSampleVideoBuffer()
        {
            m_buffer->Release();
        }

        uchar *map(MapMode mode, int *numBytes, int *bytesPerLine)
        {
            if (m_mapMode == NotMapped && mode != NotMapped) {
                BYTE *bytes;
                DWORD length;
                HRESULT hr = m_buffer->Lock(&bytes, NULL, &length);
                if (SUCCEEDED(hr)) {
                    if (numBytes)
                        *numBytes = int(length);

                    if (bytesPerLine)
                        *bytesPerLine = m_bytesPerLine;

                    m_mapMode = mode;
                    return reinterpret_cast<uchar *>(bytes);
                } else {
                    qWarning("Faild to lock mf buffer!");
                }
            }
            return 0;
        }

        void unmap()
        {
            if (m_mapMode == NotMapped)
                return;
            m_mapMode = NotMapped;
            m_buffer->Unlock();
        }

        MapMode mapMode() const
        {
            return m_mapMode;
        }

    private:
        IMFMediaBuffer *m_buffer;
        int m_bytesPerLine;
        MapMode m_mapMode;
    };

    template<class T>
    class AsyncCallback : public IMFAsyncCallback
    {
    public:
        typedef HRESULT (T::*InvokeFn)(IMFAsyncResult *pAsyncResult);

        AsyncCallback(T *pParent, InvokeFn fn) : m_pParent(pParent), m_pInvokeFn(fn)
        {
        }

        // IUnknown
        STDMETHODIMP QueryInterface(REFIID iid, void** ppv)
        {
            if (!ppv)
            {
                return E_POINTER;
            }
            if (iid == __uuidof(IUnknown))
            {
                *ppv = static_cast<IUnknown*>(static_cast<IMFAsyncCallback*>(this));
            }
            else if (iid == __uuidof(IMFAsyncCallback))
            {
                *ppv = static_cast<IMFAsyncCallback*>(this);
            }
            else
            {
                *ppv = NULL;
                return E_NOINTERFACE;
            }
            AddRef();
            return S_OK;
        }
        STDMETHODIMP_(ULONG) AddRef()
        {
            // Delegate to parent class.
            return m_pParent->AddRef();
        }
        STDMETHODIMP_(ULONG) Release()
        {
            // Delegate to parent class.
            return m_pParent->Release();
        }

        // IMFAsyncCallback methods
        STDMETHODIMP GetParameters(DWORD*, DWORD*)
        {
            // Implementation of this method is optional.
            return E_NOTIMPL;
        }

        STDMETHODIMP Invoke(IMFAsyncResult* pAsyncResult)
        {
            return (m_pParent->*m_pInvokeFn)(pAsyncResult);
        }

        T *m_pParent;
        InvokeFn m_pInvokeFn;
    };


    // Custom interface for handling IMFStreamSink::PlaceMarker calls asynchronously.
    MIDL_INTERFACE("a3ff32de-1031-438a-8b47-82f8acda59b7")
    IMarker : public IUnknown
    {
        virtual STDMETHODIMP GetMarkerType(MFSTREAMSINK_MARKER_TYPE *pType) = 0;
        virtual STDMETHODIMP GetMarkerValue(PROPVARIANT *pvar) = 0;
        virtual STDMETHODIMP GetContext(PROPVARIANT *pvar) = 0;
    };

    class Marker : public IMarker
    {
    public:
        static HRESULT Create(
            MFSTREAMSINK_MARKER_TYPE eMarkerType,
            const PROPVARIANT* pvarMarkerValue,  // Can be NULL.
            const PROPVARIANT* pvarContextValue, // Can be NULL.
            IMarker **ppMarker)
        {
            if (ppMarker == NULL)
                return E_POINTER;

            HRESULT hr = S_OK;
            Marker *pMarker = new Marker(eMarkerType);
            if (pMarker == NULL)
                hr = E_OUTOFMEMORY;

            // Copy the marker data.
            if (SUCCEEDED(hr) && pvarMarkerValue)
                hr = PropVariantCopy(&pMarker->m_varMarkerValue, pvarMarkerValue);

            if (SUCCEEDED(hr) && pvarContextValue)
                hr = PropVariantCopy(&pMarker->m_varContextValue, pvarContextValue);

            if (SUCCEEDED(hr)) {
                *ppMarker = pMarker;
                (*ppMarker)->AddRef();
            }

            if (pMarker)
                pMarker->Release();

            return hr;
        }

        // IUnknown methods.
        STDMETHODIMP QueryInterface(REFIID iid, void** ppv)
        {
            if (!ppv)
                return E_POINTER;
            if (iid == IID_IUnknown) {
                *ppv = static_cast<IUnknown*>(this);
            } else if (iid == __uuidof(IMarker)) {
                *ppv = static_cast<IMarker*>(this);
            } else {
                *ppv = NULL;
                return E_NOINTERFACE;
            }
            AddRef();
            return S_OK;
        }

        STDMETHODIMP_(ULONG) AddRef()
        {
            return InterlockedIncrement(&m_cRef);
        }

        STDMETHODIMP_(ULONG) Release()
        {
            LONG cRef = InterlockedDecrement(&m_cRef);
            if (cRef == 0)
                delete this;
            // For thread safety, return a temporary variable.
            return cRef;
        }

        STDMETHODIMP GetMarkerType(MFSTREAMSINK_MARKER_TYPE *pType)
        {
            if (pType == NULL)
                return E_POINTER;
            *pType = m_eMarkerType;
            return S_OK;
        }

        STDMETHODIMP GetMarkerValue(PROPVARIANT *pvar)
        {
            if (pvar == NULL)
                return E_POINTER;
            return PropVariantCopy(pvar, &m_varMarkerValue);
        }

        STDMETHODIMP GetContext(PROPVARIANT *pvar)
        {
            if (pvar == NULL)
                return E_POINTER;
            return PropVariantCopy(pvar, &m_varContextValue);
        }

    protected:
        MFSTREAMSINK_MARKER_TYPE m_eMarkerType;
        PROPVARIANT m_varMarkerValue;
        PROPVARIANT m_varContextValue;

    private:
        long    m_cRef;

        Marker(MFSTREAMSINK_MARKER_TYPE eMarkerType) : m_cRef(1), m_eMarkerType(eMarkerType)
        {
            PropVariantInit(&m_varMarkerValue);
            PropVariantInit(&m_varContextValue);
        }

        virtual ~Marker()
        {
            PropVariantClear(&m_varMarkerValue);
            PropVariantClear(&m_varContextValue);
        }
    };

    class MediaStream : public QObject, public IMFStreamSink, public IMFMediaTypeHandler
    {
        Q_OBJECT
        friend class MFVideoRendererControl;
    public:
        static const DWORD DEFAULT_MEDIA_STREAM_ID = 0x0;

        MediaStream(IMFMediaSink *parent, MFVideoRendererControl *rendererControl)
            : m_cRef(1)
            , m_eventQueue(0)
            , m_shutdown(false)
            , m_surface(0)
            , m_state(State_TypeNotSet)
            , m_currentFormatIndex(-1)
            , m_bytesPerLine(0)
            , m_workQueueId(0)
            , m_workQueueCB(this, &MediaStream::onDispatchWorkItem)
            , m_finalizeResult(0)
            , m_scheduledBuffer(0)
            , m_presentationClock(0)
            , m_currentMediaType(0)
            , m_prerolling(false)
            , m_prerollTargetTime(0)
            , m_startTime(0)
            , m_rendererControl(rendererControl)
        {
            m_sink = parent;

            if (FAILED(MFCreateEventQueue(&m_eventQueue)))
                qWarning("Failed to create mf event queue!");
            if (FAILED(MFAllocateWorkQueue(&m_workQueueId)))
                qWarning("Failed to allocated mf work queue!");
        }

        ~MediaStream()
        {
            Q_ASSERT(m_shutdown);
        }

        //from IUnknown
        STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject)
        {
            if (!ppvObject)
                return E_POINTER;
            if (riid == IID_IMFStreamSink) {
                *ppvObject = static_cast<IMFStreamSink*>(this);
            } else if (riid == IID_IMFMediaEventGenerator) {
                *ppvObject = static_cast<IMFMediaEventGenerator*>(this);
            } else if (riid == IID_IMFMediaTypeHandler) {
                *ppvObject = static_cast<IMFMediaTypeHandler*>(this);
            } else if (riid == IID_IUnknown) {
                *ppvObject = static_cast<IUnknown*>(static_cast<IMFStreamSink*>(this));
            } else {
                *ppvObject =  NULL;
                return E_NOINTERFACE;
            }
            AddRef();
            return S_OK;
        }

        STDMETHODIMP_(ULONG) AddRef(void)
        {
            return InterlockedIncrement(&m_cRef);
        }

        STDMETHODIMP_(ULONG) Release(void)
        {
            LONG cRef = InterlockedDecrement(&m_cRef);
            if (cRef == 0)
                delete this;
            // For thread safety, return a temporary variable.
            return cRef;
        }

        //from IMFMediaEventGenerator
        STDMETHODIMP GetEvent(
            DWORD dwFlags,
            IMFMediaEvent **ppEvent)
        {
            // GetEvent can block indefinitely, so we don't hold the lock.
            // This requires some juggling with the event queue pointer.
            HRESULT hr = S_OK;
            IMFMediaEventQueue *queue = NULL;

            m_mutex.lock();
            if (m_shutdown)
                hr = MF_E_SHUTDOWN;
            if (SUCCEEDED(hr)) {
                queue = m_eventQueue;
                queue->AddRef();
            }
            m_mutex.unlock();

            // Now get the event.
            if (SUCCEEDED(hr)) {
                hr = queue->GetEvent(dwFlags, ppEvent);
                queue->Release();
            }

            return hr;
        }

        STDMETHODIMP BeginGetEvent(
            IMFAsyncCallback *pCallback,
            IUnknown *punkState)
        {
            QMutexLocker locker(&m_mutex);
            if (m_shutdown)
                return MF_E_SHUTDOWN;
            return m_eventQueue->BeginGetEvent(pCallback, punkState);
        }

        STDMETHODIMP EndGetEvent(
            IMFAsyncResult *pResult,
            IMFMediaEvent **ppEvent)
        {
            QMutexLocker locker(&m_mutex);
            if (m_shutdown)
                return MF_E_SHUTDOWN;
            return m_eventQueue->EndGetEvent(pResult, ppEvent);
        }

        STDMETHODIMP QueueEvent(
            MediaEventType met,
            REFGUID guidExtendedType,
            HRESULT hrStatus,
            const PROPVARIANT *pvValue)
        {
#ifdef DEBUG_MEDIAFOUNDATION
            qDebug() << "MediaStream::QueueEvent" << met;
#endif
            QMutexLocker locker(&m_mutex);
            if (m_shutdown)
                return MF_E_SHUTDOWN;
            return m_eventQueue->QueueEventParamVar(met, guidExtendedType, hrStatus, pvValue);
        }

        //from IMFStreamSink
        STDMETHODIMP GetMediaSink(
            IMFMediaSink **ppMediaSink)
        {
            QMutexLocker locker(&m_mutex);
            if (m_shutdown)
                return MF_E_SHUTDOWN;
            else if (!ppMediaSink)
                return E_INVALIDARG;

            m_sink->AddRef();
            *ppMediaSink = m_sink;
            return S_OK;
        }

        STDMETHODIMP GetIdentifier(
            DWORD *pdwIdentifier)
        {
            *pdwIdentifier = MediaStream::DEFAULT_MEDIA_STREAM_ID;
            return S_OK;
        }

        STDMETHODIMP GetMediaTypeHandler(
            IMFMediaTypeHandler **ppHandler)
        {
            LPVOID handler = NULL;
            HRESULT hr = QueryInterface(IID_IMFMediaTypeHandler, &handler);
            *ppHandler = (IMFMediaTypeHandler*)(handler);
            return hr;
        }

        STDMETHODIMP ProcessSample(
            IMFSample *pSample)
        {
            if (pSample == NULL)
                return E_INVALIDARG;
            HRESULT hr = S_OK;
            QMutexLocker locker(&m_mutex);
            if (m_shutdown)
                return MF_E_SHUTDOWN;

            if (!m_prerolling) {
                hr = validateOperation(OpProcessSample);
                if (FAILED(hr))
                    return hr;
            }

            pSample->AddRef();
            m_sampleQueue.push_back(pSample);

            // Unless we are paused, start an async operation to dispatch the next sample.
            if (m_state != State_Paused)
                hr = queueAsyncOperation(OpProcessSample);

            return hr;
        }

        STDMETHODIMP PlaceMarker(
            MFSTREAMSINK_MARKER_TYPE eMarkerType,
            const PROPVARIANT *pvarMarkerValue,
            const PROPVARIANT *pvarContextValue)
        {
            HRESULT hr = S_OK;
            QMutexLocker locker(&m_mutex);
            IMarker *pMarker = NULL;
            if (m_shutdown)
                return MF_E_SHUTDOWN;

            hr = validateOperation(OpPlaceMarker);
            if (FAILED(hr))
                return hr;

            // Create a marker object and put it on the sample queue.
            hr = Marker::Create(eMarkerType, pvarMarkerValue, pvarContextValue, &pMarker);
            if (FAILED(hr))
                return hr;

            m_sampleQueue.push_back(pMarker);

            // Unless we are paused, start an async operation to dispatch the next sample/marker.
            if (m_state != State_Paused)
                hr = queueAsyncOperation(OpPlaceMarker); // Increments ref count on pOp.
            return hr;
        }

        STDMETHODIMP Flush( void)
        {
#ifdef DEBUG_MEDIAFOUNDATION
            qDebug() << "MediaStream::Flush";
#endif
            QMutexLocker locker(&m_mutex);
            if (m_shutdown)
                return MF_E_SHUTDOWN;
            // Note: Even though we are flushing data, we still need to send
            // any marker events that were queued.
            clearBufferCache();
            return processSamplesFromQueue(DropSamples);
        }

        //from IMFMediaTypeHandler
        STDMETHODIMP IsMediaTypeSupported(
            IMFMediaType *pMediaType,
            IMFMediaType **ppMediaType)
        {
            if (ppMediaType)
                *ppMediaType = NULL;
            QMutexLocker locker(&m_mutex);
            if (m_shutdown)
                return MF_E_SHUTDOWN;

            int index = getMediaTypeIndex(pMediaType);
            if (index < 0) {
                if (ppMediaType && m_mediaTypes.size() > 0) {
                    *ppMediaType = m_mediaTypes[0];
                    (*ppMediaType)->AddRef();
                }
                return MF_E_INVALIDMEDIATYPE;
            }

            BOOL compressed = TRUE;
            pMediaType->IsCompressedFormat(&compressed);
            if (compressed) {
                if (ppMediaType && (SUCCEEDED(MFCreateMediaType(ppMediaType)))) {
                    (*ppMediaType)->CopyAllItems(pMediaType);
                    (*ppMediaType)->SetUINT32(MF_MT_FIXED_SIZE_SAMPLES, TRUE);
                    (*ppMediaType)->SetUINT32(MF_MT_COMPRESSED, FALSE);
                    (*ppMediaType)->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE);
                }
                return MF_E_INVALIDMEDIATYPE;
            }

            return S_OK;
        }

        STDMETHODIMP GetMediaTypeCount(
            DWORD *pdwTypeCount)
        {
            if (pdwTypeCount == NULL)
                return E_INVALIDARG;
            QMutexLocker locker(&m_mutex);
            *pdwTypeCount = DWORD(m_mediaTypes.size());
            return S_OK;
        }

        STDMETHODIMP GetMediaTypeByIndex(
            DWORD dwIndex,
            IMFMediaType **ppType)
        {
            if (ppType == NULL)
                return E_INVALIDARG;
            HRESULT hr = S_OK;
            QMutexLocker locker(&m_mutex);
            if (m_shutdown)
                hr = MF_E_SHUTDOWN;

            if (SUCCEEDED(hr)) {
                if (dwIndex >= DWORD(m_mediaTypes.size()))
                    hr = MF_E_NO_MORE_TYPES;
            }

            if (SUCCEEDED(hr)) {
                *ppType = m_mediaTypes[dwIndex];
                (*ppType)->AddRef();
            }
            return hr;
        }

        STDMETHODIMP SetCurrentMediaType(
            IMFMediaType *pMediaType)
        {
            HRESULT hr = S_OK;
            QMutexLocker locker(&m_mutex);
            if (m_shutdown)
                return MF_E_SHUTDOWN;

            DWORD flag = MF_MEDIATYPE_EQUAL_MAJOR_TYPES |
                         MF_MEDIATYPE_EQUAL_FORMAT_TYPES |
                         MF_MEDIATYPE_EQUAL_FORMAT_DATA;

            if (m_currentMediaType && (m_currentMediaType->IsEqual(pMediaType, &flag) == S_OK))
                return S_OK;

            hr = validateOperation(OpSetMediaType);

            if (SUCCEEDED(hr)) {
                int index = getMediaTypeIndex(pMediaType);
                if (index >= 0) {
                    UINT64 size;
                    hr = pMediaType->GetUINT64(MF_MT_FRAME_SIZE, &size);
                    if (SUCCEEDED(hr)) {
                        m_currentFormatIndex = index;
                        int width = int(HI32(size));
                        int height = int(LO32(size));
                        QVideoSurfaceFormat format(QSize(width, height), m_pixelFormats[index]);
                        m_surfaceFormat = format;

                        if (FAILED(pMediaType->GetUINT32(MF_MT_DEFAULT_STRIDE, (UINT32*)&m_bytesPerLine))) {
                            m_bytesPerLine = getBytesPerLine(format);
                        }

                        m_state = State_Ready;
                        if (m_currentMediaType)
                            m_currentMediaType->Release();
                        m_currentMediaType = pMediaType;
                        pMediaType->AddRef();
                    }
                } else {
                    hr = MF_E_INVALIDREQUEST;
                }
            }
            return hr;
        }

        STDMETHODIMP GetCurrentMediaType(
            IMFMediaType **ppMediaType)
        {
            if (ppMediaType == NULL)
                return E_INVALIDARG;
            QMutexLocker locker(&m_mutex);
            if (m_shutdown)
                return MF_E_SHUTDOWN;
            if (m_currentFormatIndex < 0)
                return MF_E_NOT_INITIALIZED;
            *ppMediaType = m_currentMediaType;
            (*ppMediaType)->AddRef();
            return S_OK;
        }

        STDMETHODIMP GetMajorType(
            GUID *pguidMajorType)
        {
            if (pguidMajorType == NULL)
                return E_INVALIDARG;
            *pguidMajorType = MFMediaType_Video;
            return S_OK;
        }

        //
        void setSurface(QAbstractVideoSurface *surface)
        {
            m_mutex.lock();
            m_surface = surface;
            m_mutex.unlock();
            supportedFormatsChanged();
        }

        void setClock(IMFPresentationClock *presentationClock)
        {
            QMutexLocker locker(&m_mutex);
            if (!m_shutdown) {
                if (m_presentationClock)
                    m_presentationClock->Release();
                m_presentationClock = presentationClock;
                if (m_presentationClock)
                    m_presentationClock->AddRef();
            }
        }

        void shutdown()
        {
            QMutexLocker locker(&m_mutex);
            Q_ASSERT(!m_shutdown);

            if (m_currentMediaType) {
                m_currentMediaType->Release();
                m_currentMediaType = NULL;
                m_currentFormatIndex = -1;
            }

            if (m_eventQueue)
                m_eventQueue->Shutdown();

            MFUnlockWorkQueue(m_workQueueId);

            if (m_presentationClock) {
                m_presentationClock->Release();
                m_presentationClock = NULL;
            }

            if (m_scheduledBuffer) {
                m_scheduledBuffer->Release();
                m_scheduledBuffer = NULL;
            }

            clearMediaTypes();
            clearSampleQueue();
            clearBufferCache();

            if (m_eventQueue) {
                m_eventQueue->Release();
                m_eventQueue = NULL;
            }

            m_shutdown = true;
        }

        HRESULT startPreroll(MFTIME hnsUpcomingStartTime)
        {
            QMutexLocker locker(&m_mutex);
            HRESULT hr = validateOperation(OpPreroll);
            if (SUCCEEDED(hr)) {
                m_prerollTargetTime = hnsUpcomingStartTime;
                hr = queueAsyncOperation(OpPreroll);
            }
            return hr;
        }

        HRESULT finalize(IMFAsyncCallback *pCallback, IUnknown *punkState)
        {
            QMutexLocker locker(&m_mutex);
            HRESULT hr = S_OK;
            hr = validateOperation(OpFinalize);
            if (SUCCEEDED(hr) && m_finalizeResult != NULL)
                hr = MF_E_INVALIDREQUEST;  // The operation is already pending.

            // Create and store the async result object.
            if (SUCCEEDED(hr))
                hr = MFCreateAsyncResult(NULL, pCallback, punkState, &m_finalizeResult);

            if (SUCCEEDED(hr)) {
                m_state = State_Finalized;
                hr = queueAsyncOperation(OpFinalize);
            }
            return hr;
        }

        HRESULT start(MFTIME start)
        {
#ifdef DEBUG_MEDIAFOUNDATION
            qDebug() << "MediaStream::start" << start;
#endif
            HRESULT hr = S_OK;
            QMutexLocker locker(&m_mutex);
            if (m_rate != 0)
                hr = validateOperation(OpStart);

            if (SUCCEEDED(hr)) {
                MFTIME sysTime;
                if (start != PRESENTATION_CURRENT_POSITION)
                    m_startTime = start;        // Cache the start time.
                else
                    m_presentationClock->GetCorrelatedTime(0, &m_startTime, &sysTime);
                m_state = State_Started;
                hr = queueAsyncOperation(OpStart);
            }
            return hr;
        }

        HRESULT restart()
        {
#ifdef DEBUG_MEDIAFOUNDATION
            qDebug() << "MediaStream::restart";
#endif
            QMutexLocker locker(&m_mutex);
            HRESULT hr = validateOperation(OpRestart);
            if (SUCCEEDED(hr)) {
                m_state = State_Started;
                hr = queueAsyncOperation(OpRestart);
            }
            return hr;
        }

        HRESULT stop()
        {
#ifdef DEBUG_MEDIAFOUNDATION
            qDebug() << "MediaStream::stop";
#endif
            QMutexLocker locker(&m_mutex);
            HRESULT hr = validateOperation(OpStop);
            if (SUCCEEDED(hr)) {
                m_state = State_Stopped;
                hr = queueAsyncOperation(OpStop);
            }
            return hr;
        }

        HRESULT pause()
        {
#ifdef DEBUG_MEDIAFOUNDATION
            qDebug() << "MediaStream::pause";
#endif
            QMutexLocker locker(&m_mutex);
            HRESULT hr = validateOperation(OpPause);
            if (SUCCEEDED(hr)) {
                m_state = State_Paused;
                hr = queueAsyncOperation(OpPause);
            }
            return hr;
        }

        HRESULT setRate(float rate)
        {
#ifdef DEBUG_MEDIAFOUNDATION
            qDebug() << "MediaStream::setRate" << rate;
#endif
            QMutexLocker locker(&m_mutex);
            m_rate = rate;
            queueEvent(MEStreamSinkRateChanged, GUID_NULL, S_OK, NULL);
            return S_OK;
        }

        void supportedFormatsChanged()
        {
            QMutexLocker locker(&m_mutex);
            m_pixelFormats.clear();
            clearMediaTypes();
            QList<QVideoFrame::PixelFormat> formats = m_surface->supportedPixelFormats();
            foreach (QVideoFrame::PixelFormat format, formats) {
                IMFMediaType *mediaType;
                if (FAILED(MFCreateMediaType(&mediaType))) {
                    qWarning("Failed to create mf media type!");
                    continue;
                }
                mediaType->SetUINT32(MF_MT_FIXED_SIZE_SAMPLES, TRUE);
                mediaType->SetUINT32(MF_MT_COMPRESSED, FALSE);
                mediaType->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE);
                mediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
                switch (format) {
                    case QVideoFrame::Format_ARGB32:
                    case QVideoFrame::Format_ARGB32_Premultiplied:
                        mediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_ARGB32);
                        break;
                    case QVideoFrame::Format_RGB32:
                        mediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32);
                        break;
                    case QVideoFrame::Format_RGB24:
                        mediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB24);
                        break;
                    case QVideoFrame::Format_RGB565:
                        mediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB565);
                        break;
                    case QVideoFrame::Format_RGB555:
                        mediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB555);
                        break;
                    case QVideoFrame::Format_AYUV444:
                    case QVideoFrame::Format_AYUV444_Premultiplied:
                        mediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_AYUV);
                        break;
                    case QVideoFrame::Format_YUV420P:
                        mediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_I420);
                        break;
                    case QVideoFrame::Format_UYVY:
                        mediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_UYVY);
                        break;
                    case QVideoFrame::Format_YV12:
                        mediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_YV12);
                        break;
                    case QVideoFrame::Format_NV12:
                        mediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_NV12);
                        break;
                    default:
                        mediaType->Release();
                        continue;
                }
                m_pixelFormats.push_back(format);
                m_mediaTypes.push_back(mediaType);
            }
        }

        void present()
        {
            QMutexLocker locker(&m_mutex);
            if (!m_scheduledBuffer)
                return;
            m_surface->present(QVideoFrame(
                    new MediaSampleVideoBuffer(m_scheduledBuffer, m_bytesPerLine),
                    m_surfaceFormat.frameSize(),
                    m_surfaceFormat.pixelFormat()));
            m_scheduledBuffer->Release();
            m_scheduledBuffer = NULL;
            if (m_rate != 0)
                schedulePresentation(true);
        }

        enum
        {
            StartSurface = QEvent::User,
            StopSurface,
            FlushSurface,
            PresentSurface
        };

        class PresentEvent : public QEvent
        {
        public:
            PresentEvent(MFTIME targetTime)
                : QEvent(QEvent::Type(PresentSurface))
                , m_time(targetTime)
            {
            }

            int targetTime()
            {
                return m_time;
            }

        private:
            MFTIME m_time;
        };

    protected:
        void customEvent(QEvent *event)
        {
            QMutexLocker locker(&m_mutex);
            if (event->type() == StartSurface) {
                if (m_state == State_WaitForSurfaceStart) {
                    m_startResult = startSurface();
                    queueAsyncOperation(OpStart);
                }
            } else if (event->type() == StopSurface) {
                stopSurface();
            } else {
               QObject::customEvent(event);
            }
        }
        HRESULT m_startResult;

    private:
        HRESULT startSurface()
        {
            if (!m_surface->isFormatSupported(m_surfaceFormat))
                return S_FALSE;
            if (!m_surface->start(m_surfaceFormat))
                return S_FALSE;
            return S_OK;
        }

        void stopSurface()
        {
            m_surface->stop();
        }

        enum FlushState
        {
            DropSamples = 0,
            WriteSamples
        };

        // State enum: Defines the current state of the stream.
        enum State
        {
            State_TypeNotSet = 0,    // No media type is set
            State_Ready,             // Media type is set, Start has never been called.
            State_Started,
            State_Paused,
            State_Stopped,
            State_WaitForSurfaceStart,
            State_Finalized,
            State_Count = State_Finalized + 1    // Number of states
        };

        // StreamOperation: Defines various operations that can be performed on the stream.
        enum StreamOperation
        {
            OpSetMediaType = 0,
            OpPreroll,
            OpStart,
            OpRestart,
            OpPause,
            OpStop,
            OpSetRate,
            OpProcessSample,
            OpPlaceMarker,
            OpFinalize,

            Op_Count = OpFinalize + 1  // Number of operations
        };

        // AsyncOperation:
        // Used to queue asynchronous operations. When we call MFPutWorkItem, we use this
        // object for the callback state (pState). Then, when the callback is invoked,
        // we can use the object to determine which asynchronous operation to perform.
        class AsyncOperation : public IUnknown
        {
        public:
            AsyncOperation(StreamOperation op)
                :m_cRef(1), m_op(op)
            {
            }

            StreamOperation m_op;   // The operation to perform.

            //from IUnknown
            STDMETHODIMP QueryInterface(REFIID iid, void** ppv)
            {
                if (!ppv)
                    return E_POINTER;
                if (iid == IID_IUnknown) {
                    *ppv = static_cast<IUnknown*>(this);
                } else {
                    *ppv = NULL;
                    return E_NOINTERFACE;
                }
                AddRef();
                return S_OK;
            }
            STDMETHODIMP_(ULONG) AddRef()
            {
                return InterlockedIncrement(&m_cRef);
            }
            STDMETHODIMP_(ULONG) Release()
            {
                ULONG uCount = InterlockedDecrement(&m_cRef);
                if (uCount == 0)
                    delete this;
                // For thread safety, return a temporary variable.
                return uCount;
            }

        private:
            long    m_cRef;
            virtual ~AsyncOperation()
            {
                Q_ASSERT(m_cRef == 0);
            }
        };

        // ValidStateMatrix: Defines a look-up table that says which operations
        // are valid from which states.
        static BOOL ValidStateMatrix[State_Count][Op_Count];

        long m_cRef;
        QMutex m_mutex;

        IMFMediaType *m_currentMediaType;
        State m_state;
        IMFMediaSink *m_sink;
        IMFMediaEventQueue *m_eventQueue;
        DWORD m_workQueueId;
        AsyncCallback<MediaStream> m_workQueueCB;
        QList<IUnknown*> m_sampleQueue;
        IMFAsyncResult   *m_finalizeResult;         // Result object for Finalize operation.
        MFTIME           m_startTime;               // Presentation time when the clock started.

        bool m_shutdown;
        QList<IMFMediaType*> m_mediaTypes;
        QList<QVideoFrame::PixelFormat> m_pixelFormats;
        int m_currentFormatIndex;
        int m_bytesPerLine;
        QVideoSurfaceFormat m_surfaceFormat;
        QAbstractVideoSurface* m_surface;
        MFVideoRendererControl *m_rendererControl;

        void clearMediaTypes()
        {
            foreach (IMFMediaType* mediaType, m_mediaTypes)
                mediaType->Release();
            m_mediaTypes.clear();
        }

        int getMediaTypeIndex(IMFMediaType *mt)
        {
            GUID majorType;
            if (FAILED(mt->GetMajorType(&majorType)))
                return -1;
            if (majorType != MFMediaType_Video)
                return -1;

            GUID subType;
            if (FAILED(mt->GetGUID(MF_MT_SUBTYPE, &subType)))
                return -1;

            for (int index = 0; index < m_mediaTypes.size(); ++index) {
                GUID st;
                m_mediaTypes[index]->GetGUID(MF_MT_SUBTYPE, &st);
                if (st == subType)
                    return index;
            }
            return -1;
        }

        int getBytesPerLine(const QVideoSurfaceFormat &format)
        {
            switch (format.pixelFormat()) {
            // 32 bpp packed formats.
            case QVideoFrame::Format_RGB32:
            case QVideoFrame::Format_AYUV444:
                return format.frameWidth() * 4;
            // 24 bpp packed formats.
            case QVideoFrame::Format_RGB24:
                return PAD_TO_DWORD(format.frameWidth() * 3);
            // 16 bpp packed formats.
            case QVideoFrame::Format_RGB565:
            case QVideoFrame::Format_RGB555:
            case QVideoFrame::Format_YUYV:
            case QVideoFrame::Format_UYVY:
                return PAD_TO_DWORD(format.frameWidth() * 2);
            // Planar formats.
            case QVideoFrame::Format_IMC1:
            case QVideoFrame::Format_IMC2:
            case QVideoFrame::Format_IMC3:
            case QVideoFrame::Format_IMC4:
            case QVideoFrame::Format_YV12:
            case QVideoFrame::Format_NV12:
            case QVideoFrame::Format_YUV420P:
                return PAD_TO_DWORD(format.frameWidth());
            default:
                return 0;
            }
        }

        // Callback for MFPutWorkItem.
        HRESULT onDispatchWorkItem(IMFAsyncResult* pAsyncResult)
        {
            QMutexLocker locker(&m_mutex);
            HRESULT hr = S_OK;
            IUnknown *pState = NULL;
            hr = pAsyncResult->GetState(&pState);
            if (SUCCEEDED(hr)) {
                // The state object is an AsncOperation object.
                AsyncOperation *pOp = (AsyncOperation*)pState;
                StreamOperation op = pOp->m_op;
                switch (op)    {
                case OpStart:
                    endPreroll(S_FALSE);
                    if (m_state == State_WaitForSurfaceStart) {
                        hr = m_startResult;
                        m_state = State_Started;
                    } else if (!m_surface->isActive()) {
                        if (thread() == QThread::currentThread()) {
                            hr = startSurface();
                        }
                        else {
                            m_state = State_WaitForSurfaceStart;
                            QCoreApplication::postEvent(m_rendererControl, new QChildEvent(QEvent::Type(StartSurface), this));
                            break;
                        }
                    }
                case OpRestart:
                    endPreroll(S_FALSE);
                    if (SUCCEEDED(hr)) {
                        // Send MEStreamSinkStarted.
                        hr = queueEvent(MEStreamSinkStarted, GUID_NULL, hr, NULL);
                        // Kick things off by requesting samples...
                        schedulePresentation(true);
                        // There might be samples queue from earlier (ie, while paused).
                        if (SUCCEEDED(hr))
                            hr = processSamplesFromQueue(WriteSamples);
                    }
                    break;
                case OpPreroll:
                    beginPreroll();
                    break;
                case OpStop:
                    // Drop samples from queue.
                    hr = processSamplesFromQueue(DropSamples);
                    if (m_scheduledBuffer) {
                        m_scheduledBuffer->Release();
                        m_scheduledBuffer = NULL;
                    }
                    // Send the event even if the previous call failed.
                    hr = queueEvent(MEStreamSinkStopped, GUID_NULL, hr, NULL);
                    if (m_surface->isActive()) {
                        if (thread() == QThread::currentThread()) {
                            stopSurface();
                        }
                        else {
                            QCoreApplication::postEvent(m_rendererControl, new QChildEvent(QEvent::Type(StopSurface), this));
                        }
                    }
                    break;
                case OpPause:
                    hr = queueEvent(MEStreamSinkPaused, GUID_NULL, hr, NULL);
                    break;
                case OpSetRate:
                    //TODO:
                    break;
                case OpProcessSample:
                case OpPlaceMarker:
                    hr = dispatchProcessSample(pOp);
                    break;
                case OpFinalize:
                    endPreroll(S_FALSE);
                    hr = dispatchFinalize(pOp);
                    break;
                }
            }

            if (pState)
                pState->Release();
            return hr;
        }


        HRESULT queueEvent(MediaEventType met, REFGUID guidExtendedType, HRESULT hrStatus, const PROPVARIANT* pvValue)
        {
            HRESULT hr = S_OK;
            if (m_shutdown)
                hr = MF_E_SHUTDOWN;
            if (SUCCEEDED(hr))
                hr = m_eventQueue->QueueEventParamVar(met, guidExtendedType, hrStatus, pvValue);
            return hr;
        }

        HRESULT validateOperation(StreamOperation op)
        {
            Q_ASSERT(!m_shutdown);
            if (ValidStateMatrix[m_state][op])
                return S_OK;
            else
                return MF_E_INVALIDREQUEST;
        }

        HRESULT queueAsyncOperation(StreamOperation op)
        {
            HRESULT hr = S_OK;
            AsyncOperation *asyncOp = new AsyncOperation(op);
            if (asyncOp == NULL)
                hr = E_OUTOFMEMORY;

            if (SUCCEEDED(hr))
                hr = MFPutWorkItem(m_workQueueId, &m_workQueueCB, asyncOp);

            if (asyncOp)
                asyncOp->Release();

            return hr;
        }

        HRESULT processSamplesFromQueue(FlushState bFlushData)
        {
            HRESULT hr = S_OK;
            QList<IUnknown*>::Iterator pos = m_sampleQueue.begin();
            // Enumerate all of the samples/markers in the queue.
            while (pos != m_sampleQueue.end()) {
                IUnknown *pUnk = NULL;
                IMarker  *pMarker = NULL;
                IMFSample *pSample = NULL;
                pUnk = *pos;
                // Figure out if this is a marker or a sample.
                if (SUCCEEDED(hr))    {
                    hr = pUnk->QueryInterface(__uuidof(IMarker), (void**)&pMarker);
                    if (hr == E_NOINTERFACE)
                        hr = pUnk->QueryInterface(IID_IMFSample, (void**)&pSample);
                }

                // Now handle the sample/marker appropriately.
                if (SUCCEEDED(hr)) {
                    if (pMarker) {
                        hr = sendMarkerEvent(pMarker, bFlushData);
                    } else {
                        Q_ASSERT(pSample != NULL);    // Not a marker, must be a sample
                        if (bFlushData == WriteSamples)
                            hr = processSampleData(pSample);
                    }
                }
                if (pMarker)
                    pMarker->Release();
                if (pSample)
                    pSample->Release();

                if (FAILED(hr))
                    break;

                pos++;
            }

            clearSampleQueue();
            return hr;
        }

        void beginPreroll()
        {
            if (m_prerolling)
                return;
            m_prerolling = true;
            clearSampleQueue();
            clearBufferCache();
            queueEvent(MEStreamSinkRequestSample, GUID_NULL, S_OK, NULL);
        }

        void endPreroll(HRESULT hrStatus)
        {
            if (!m_prerolling)
                return;
            m_prerolling = false;
            queueEvent(MEStreamSinkPrerolled, GUID_NULL, hrStatus, NULL);
        }
        MFTIME m_prerollTargetTime;
        bool m_prerolling;

        void clearSampleQueue() {
            foreach (IUnknown* sample, m_sampleQueue)
                sample->Release();
            m_sampleQueue.clear();
        }

        HRESULT sendMarkerEvent(IMarker *pMarker, FlushState FlushState)
        {
            HRESULT hr = S_OK;
            HRESULT hrStatus = S_OK;  // Status code for marker event.
            if (FlushState == DropSamples)
                hrStatus = E_ABORT;

            PROPVARIANT var;
            PropVariantInit(&var);

            // Get the context data.
            hr = pMarker->GetContext(&var);

            if (SUCCEEDED(hr))
                hr = queueEvent(MEStreamSinkMarker, GUID_NULL, hrStatus, &var);

            PropVariantClear(&var);
            return hr;
        }

        HRESULT dispatchProcessSample(AsyncOperation* pOp)
        {
            HRESULT hr = S_OK;
            Q_ASSERT(pOp != NULL);
            hr = processSamplesFromQueue(WriteSamples);
            // We are in the middle of an asynchronous operation, so if something failed, send an error.
            if (FAILED(hr))
                hr = queueEvent(MEError, GUID_NULL, hr, NULL);

            return hr;
        }

        HRESULT dispatchFinalize(AsyncOperation*)
        {
            HRESULT hr = S_OK;
            // Write any samples left in the queue...
            hr = processSamplesFromQueue(WriteSamples);

            // Set the async status and invoke the callback.
            m_finalizeResult->SetStatus(hr);
            hr = MFInvokeCallback(m_finalizeResult);
            return hr;
        }

        HRESULT processSampleData(IMFSample *pSample)
        {
            LONGLONG time;
            HRESULT hr = pSample->GetSampleTime(&time);

            if (m_prerolling) {
                if (SUCCEEDED(hr) && time >= m_prerollTargetTime) {
                    IMFMediaBuffer *pBuffer = NULL;
                    hr = pSample->ConvertToContiguousBuffer(&pBuffer);
                    if (SUCCEEDED(hr)) {
                        SampleBuffer sb;
                        sb.m_buffer = pBuffer;
                        sb.m_time = time;
                        m_bufferCache.push_back(sb);
                        endPreroll(S_OK);
                    }
                } else {
                    queueEvent(MEStreamSinkRequestSample, GUID_NULL, S_OK, NULL);
                }
            } else {
                bool requestSample = true;
                // If the time stamp is too early, just discard this sample.
                if (SUCCEEDED(hr) && time >= m_startTime) {
                    IMFMediaBuffer *pBuffer = NULL;
                    hr = pSample->ConvertToContiguousBuffer(&pBuffer);
                    if (SUCCEEDED(hr)) {
                        SampleBuffer sb;
                        sb.m_buffer = pBuffer;
                        sb.m_time = time;
                        m_bufferCache.push_back(sb);
                    }
                    if (m_rate == 0)
                        requestSample = false;
                }
                schedulePresentation(requestSample);
            }
            return hr;
        }

        class SampleBuffer
        {
        public:
            IMFMediaBuffer *m_buffer;
            LONGLONG m_time;
        };
        QList<SampleBuffer> m_bufferCache;
        static const int BUFFER_CACHE_SIZE = 2;

        void clearBufferCache()
        {
            foreach (SampleBuffer sb, m_bufferCache)
                sb.m_buffer->Release();
            m_bufferCache.clear();
        }

        void schedulePresentation(bool requestSample)
        {
            if (m_state == State_Paused)
                return;
            if (!m_scheduledBuffer) {
                //get time from presentation time
                MFTIME currentTime = m_startTime, sysTime;
                bool timeOK = true;
                if (m_rate != 0) {
                    if (FAILED(m_presentationClock->GetCorrelatedTime(0, &currentTime, &sysTime)))
                        timeOK = false;
                }
                while (!m_bufferCache.isEmpty()) {
                    SampleBuffer sb = m_bufferCache.first();
                    m_bufferCache.pop_front();
                    if (timeOK && currentTime > sb.m_time) {
                        sb.m_buffer->Release();
                        qDebug() << "currentPresentTime =" << float(currentTime / 10000) * 0.001f << " and sampleTime is" << float(sb.m_time / 10000) * 0.001f;
                        continue;
                    }
                    m_scheduledBuffer = sb.m_buffer;
                    QCoreApplication::postEvent(m_rendererControl, new PresentEvent(sb.m_time));
                    if (m_rate == 0)
                        queueEvent(MEStreamSinkScrubSampleComplete, GUID_NULL, S_OK, NULL);
                    break;
                }
            }
            if (requestSample && m_bufferCache.size() < BUFFER_CACHE_SIZE)
                queueEvent(MEStreamSinkRequestSample, GUID_NULL, S_OK, NULL);
        }
        IMFMediaBuffer *m_scheduledBuffer;
        IMFPresentationClock *m_presentationClock;
        float m_rate;
    };

    BOOL MediaStream::ValidStateMatrix[MediaStream::State_Count][MediaStream::Op_Count] =
    {
        // States:    Operations:
        //                         SetType  Start   Preroll, Restart   Pause     Stop        SetRate  Sample    Marker    Finalize
        /* NotSet */                TRUE,   FALSE,  FALSE,    FALSE,    FALSE,    FALSE,    FALSE,      FALSE,    FALSE,    FALSE,

        /* Ready */                 TRUE,   TRUE,   TRUE,       FALSE,    TRUE,     TRUE,     TRUE,      FALSE,    TRUE,     TRUE,

        /* Start */                 FALSE,  TRUE,   FALSE,      FALSE,    TRUE,     TRUE,     TRUE,       TRUE,     TRUE,     TRUE,

        /* Pause */                 FALSE,  TRUE,   TRUE,      TRUE,     TRUE,     TRUE,     TRUE,       TRUE,     TRUE,     TRUE,

        /* Stop */                  FALSE,  TRUE,   TRUE,       FALSE,    FALSE,    TRUE,     TRUE,      FALSE,    TRUE,     TRUE,

        /*WaitForSurfaceStart*/     FALSE,  FALSE,  TRUE,        FALSE,    FALSE,    TRUE,     TRUE,      FALSE,    FALSE,    TRUE,

        /* Final */                 FALSE,  FALSE,  FALSE,      FALSE,    FALSE,    FALSE,    FALSE,      FALSE,    FALSE,    FALSE

        // Note about states:
        // 1. OnClockRestart should only be called from paused state.
        // 2. While paused, the sink accepts samples but does not process them.
    };

    class MediaSink : public IMFFinalizableMediaSink, public IMFClockStateSink, public IMFMediaSinkPreroll
    {
    public:
        MediaSink(MFVideoRendererControl *rendererControl)
            : m_cRef(1)
            , m_shutdown(false)
            , m_presentationClock(0)
            , m_playRate(1)
        {
            m_stream = new MediaStream(this, rendererControl);
        }

        ~MediaSink()
        {
            Q_ASSERT(m_shutdown);
        }

        void setSurface(QAbstractVideoSurface *surface)
        {
            QMutexLocker locker(&m_mutex);
            if (m_shutdown)
                return;
            m_stream->setSurface(surface);
        }

        void supportedFormatsChanged()
        {
            QMutexLocker locker(&m_mutex);
            if (m_shutdown)
                return;
            m_stream->supportedFormatsChanged();
        }

        void present()
        {
            QMutexLocker locker(&m_mutex);
            if (m_shutdown)
                return;
            m_stream->present();
        }

        MFTIME getTime()
        {
            QMutexLocker locker(&m_mutex);
            if (!m_presentationClock)
                return 0;
            MFTIME time, sysTime;
            m_presentationClock->GetCorrelatedTime(0, &time, &sysTime);
            return time;
        }

        float getPlayRate()
        {
            QMutexLocker locker(&m_mutex);
            return m_playRate;
        }

        //from IUnknown
        STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject)
        {
            if (!ppvObject)
                return E_POINTER;
            if (riid == IID_IMFMediaSink) {
                *ppvObject = static_cast<IMFMediaSink*>(this);
            } else if (riid == IID_IMFMediaSinkPreroll) {
                *ppvObject = static_cast<IMFMediaSinkPreroll*>(this);
            } else if (riid == IID_IMFClockStateSink) {
                *ppvObject = static_cast<IMFClockStateSink*>(this);
            } else if (riid == IID_IUnknown) {
                *ppvObject = static_cast<IUnknown*>(static_cast<IMFFinalizableMediaSink*>(this));
            } else {
                *ppvObject =  NULL;
                return E_NOINTERFACE;
            }
            AddRef();
            return S_OK;
        }

        STDMETHODIMP_(ULONG) AddRef(void)
        {
            return InterlockedIncrement(&m_cRef);
        }

        STDMETHODIMP_(ULONG) Release(void)
        {
            LONG cRef = InterlockedDecrement(&m_cRef);
            if (cRef == 0)
                delete this;
            // For thread safety, return a temporary variable.
            return cRef;
        }



        //IMFMediaSinkPreroll
        STDMETHODIMP NotifyPreroll(MFTIME hnsUpcomingStartTime)
        {
            QMutexLocker locker(&m_mutex);
            if (m_shutdown)
                return MF_E_SHUTDOWN;
            return m_stream->startPreroll(hnsUpcomingStartTime);
        }

        //from IMFFinalizableMediaSink
        STDMETHODIMP BeginFinalize(IMFAsyncCallback *pCallback, IUnknown *punkState)
        {
            QMutexLocker locker(&m_mutex);
            if (m_shutdown)
                return MF_E_SHUTDOWN;
            return m_stream->finalize(pCallback, punkState);
        }

        STDMETHODIMP EndFinalize(IMFAsyncResult *pResult)
        {
            HRESULT hr = S_OK;
            // Return the status code from the async result.
            if (pResult == NULL)
                hr = E_INVALIDARG;
            else
                hr = pResult->GetStatus();
            return hr;
        }

        //from IMFMediaSink
        STDMETHODIMP GetCharacteristics(
            DWORD *pdwCharacteristics)
        {
            QMutexLocker locker(&m_mutex);
            if (m_shutdown)
                return MF_E_SHUTDOWN;
            *pdwCharacteristics = MEDIASINK_FIXED_STREAMS | MEDIASINK_CAN_PREROLL;
            return S_OK;
        }

        STDMETHODIMP AddStreamSink(
            DWORD,
            IMFMediaType *,
            IMFStreamSink **)
        {
            QMutexLocker locker(&m_mutex);
            return m_shutdown ? MF_E_SHUTDOWN : MF_E_STREAMSINKS_FIXED;
        }

        STDMETHODIMP RemoveStreamSink(
            DWORD)
        {
            QMutexLocker locker(&m_mutex);
            return m_shutdown ? MF_E_SHUTDOWN : MF_E_STREAMSINKS_FIXED;
        }

        STDMETHODIMP GetStreamSinkCount(
            DWORD *pcStreamSinkCount)
        {
            QMutexLocker locker(&m_mutex);
            if (m_shutdown)
                return MF_E_SHUTDOWN;
            *pcStreamSinkCount = 1;
            return S_OK;
        }

        STDMETHODIMP GetStreamSinkByIndex(
            DWORD dwIndex,
            IMFStreamSink **ppStreamSink)
        {
            QMutexLocker locker(&m_mutex);
            if (m_shutdown)
                return MF_E_SHUTDOWN;

            if (dwIndex != 0)
                return MF_E_INVALIDINDEX;

            *ppStreamSink = m_stream;
            m_stream->AddRef();
            return S_OK;
        }

        STDMETHODIMP GetStreamSinkById(
            DWORD dwStreamSinkIdentifier,
            IMFStreamSink **ppStreamSink)
        {
            if (ppStreamSink == NULL)
                return E_INVALIDARG;
            if (dwStreamSinkIdentifier != MediaStream::DEFAULT_MEDIA_STREAM_ID)
                return MF_E_INVALIDSTREAMNUMBER;

            QMutexLocker locker(&m_mutex);
            if (m_shutdown)
                return MF_E_SHUTDOWN;

            *ppStreamSink = m_stream;
            m_stream->AddRef();
            return S_OK;
        }

        STDMETHODIMP SetPresentationClock(
            IMFPresentationClock *pPresentationClock)
        {
            QMutexLocker locker(&m_mutex);
            if (m_shutdown)
                return MF_E_SHUTDOWN;

            if (m_presentationClock) {
                m_presentationClock->RemoveClockStateSink(this);
                m_presentationClock->Release();
            }
            m_presentationClock = pPresentationClock;
            if (m_presentationClock) {
                m_presentationClock->AddRef();
                m_presentationClock->AddClockStateSink(this);
            }
            m_stream->setClock(m_presentationClock);
            return S_OK;
        }

        STDMETHODIMP GetPresentationClock(
            IMFPresentationClock **ppPresentationClock)
        {
            QMutexLocker locker(&m_mutex);
            if (m_shutdown)
                return MF_E_SHUTDOWN;
            *ppPresentationClock = m_presentationClock;
            if (m_presentationClock) {
                m_presentationClock->AddRef();
                return S_OK;
            }
            return MF_E_NO_CLOCK;
        }

        STDMETHODIMP Shutdown(void)
        {
            QMutexLocker locker(&m_mutex);
            if (m_shutdown)
                return MF_E_SHUTDOWN;

            m_stream->shutdown();
            if (m_presentationClock) {
                m_presentationClock->Release();
                m_presentationClock = NULL;
            }
            m_stream->Release();
            m_stream = NULL;
            m_shutdown = true;
            return S_OK;
        }

        // IMFClockStateSink methods
        STDMETHODIMP OnClockStart(MFTIME, LONGLONG llClockStartOffset)
        {
            QMutexLocker locker(&m_mutex);
            if (m_shutdown)
                return MF_E_SHUTDOWN;
            return m_stream->start(llClockStartOffset);
        }

        STDMETHODIMP OnClockStop(MFTIME)
        {
            QMutexLocker locker(&m_mutex);
            if (m_shutdown)
                return MF_E_SHUTDOWN;
            return m_stream->stop();
        }

        STDMETHODIMP OnClockPause(MFTIME)
        {
            QMutexLocker locker(&m_mutex);
            if (m_shutdown)
                return MF_E_SHUTDOWN;
            return m_stream->pause();
        }

        STDMETHODIMP OnClockRestart(MFTIME)
        {
            QMutexLocker locker(&m_mutex);
            if (m_shutdown)
                return MF_E_SHUTDOWN;
            return m_stream->restart();
        }

        STDMETHODIMP OnClockSetRate(MFTIME, float flRate)
        {
            QMutexLocker locker(&m_mutex);
            if (m_shutdown)
                return MF_E_SHUTDOWN;
            m_playRate = flRate;
            return m_stream->setRate(flRate);
        }

    private:
        long    m_cRef;
        QMutex  m_mutex;
        bool    m_shutdown;
        IMFPresentationClock *m_presentationClock;
        MediaStream *m_stream;
        float   m_playRate;
    };

    class VideoRendererActivate : public IMFActivate
    {
    public:
        VideoRendererActivate(MFVideoRendererControl *rendererControl)
            : m_cRef(1)
            , m_sink(0)
            , m_rendererControl(rendererControl)
            , m_attributes(0)
        {
            MFCreateAttributes(&m_attributes, 0);
            m_sink = new MediaSink(rendererControl);
        }

        ~VideoRendererActivate()
        {
            m_attributes->Release();
        }

        //from IUnknown
        STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject)
        {
            if (!ppvObject)
                return E_POINTER;
            if (riid == IID_IMFActivate) {
                *ppvObject = static_cast<IMFActivate*>(this);
            } else if (riid == IID_IMFAttributes) {
                *ppvObject = static_cast<IMFAttributes*>(this);
            } else if (riid == IID_IUnknown) {
                *ppvObject = static_cast<IUnknown*>(static_cast<IMFActivate*>(this));
            } else {
                *ppvObject =  NULL;
                return E_NOINTERFACE;
            }
            AddRef();
            return S_OK;
        }

        STDMETHODIMP_(ULONG) AddRef(void)
        {
            return InterlockedIncrement(&m_cRef);
        }

        STDMETHODIMP_(ULONG) Release(void)
        {
            LONG cRef = InterlockedDecrement(&m_cRef);
            if (cRef == 0)
                delete this;
            // For thread safety, return a temporary variable.
            return cRef;
        }

        //from IMFActivate
        STDMETHODIMP ActivateObject(REFIID riid, void **ppv)
        {
            if (!ppv)
                return E_INVALIDARG;
            QMutexLocker locker(&m_mutex);
            if (!m_sink) {
                m_sink = new MediaSink(m_rendererControl);
                if (m_surface)
                    m_sink->setSurface(m_surface);
            }
            return m_sink->QueryInterface(riid, ppv);
        }

        STDMETHODIMP ShutdownObject(void)
        {
            QMutexLocker locker(&m_mutex);
            HRESULT hr = S_OK;
            if (m_sink) {
                hr = m_sink->Shutdown();
                m_sink->Release();
                m_sink = NULL;
            }
            return hr;
        }

        STDMETHODIMP DetachObject(void)
        {
            QMutexLocker locker(&m_mutex);
            if (m_sink) {
                m_sink->Release();
                m_sink = NULL;
            }
            return S_OK;
        }

        //from IMFAttributes
        STDMETHODIMP GetItem(
            REFGUID guidKey,
            PROPVARIANT *pValue)
        {
            return m_attributes->GetItem(guidKey, pValue);
        }

        STDMETHODIMP GetItemType(
            REFGUID guidKey,
            MF_ATTRIBUTE_TYPE *pType)
        {
            return m_attributes->GetItemType(guidKey, pType);
        }

        STDMETHODIMP CompareItem(
            REFGUID guidKey,
            REFPROPVARIANT Value,
            BOOL *pbResult)
        {
            return m_attributes->CompareItem(guidKey, Value, pbResult);
        }

        STDMETHODIMP Compare(
            IMFAttributes *pTheirs,
            MF_ATTRIBUTES_MATCH_TYPE MatchType,
            BOOL *pbResult)
        {
            return m_attributes->Compare(pTheirs, MatchType, pbResult);
        }

        STDMETHODIMP GetUINT32(
            REFGUID guidKey,
            UINT32 *punValue)
        {
            return m_attributes->GetUINT32(guidKey, punValue);
        }

        STDMETHODIMP GetUINT64(
            REFGUID guidKey,
            UINT64 *punValue)
        {
            return m_attributes->GetUINT64(guidKey, punValue);
        }

        STDMETHODIMP GetDouble(
            REFGUID guidKey,
            double *pfValue)
        {
            return m_attributes->GetDouble(guidKey, pfValue);
        }

        STDMETHODIMP GetGUID(
            REFGUID guidKey,
            GUID *pguidValue)
        {
            return m_attributes->GetGUID(guidKey, pguidValue);
        }

        STDMETHODIMP GetStringLength(
            REFGUID guidKey,
            UINT32 *pcchLength)
        {
            return m_attributes->GetStringLength(guidKey, pcchLength);
        }

        STDMETHODIMP GetString(
            REFGUID guidKey,
            LPWSTR pwszValue,
            UINT32 cchBufSize,
            UINT32 *pcchLength)
        {
            return m_attributes->GetString(guidKey, pwszValue, cchBufSize, pcchLength);
        }

        STDMETHODIMP GetAllocatedString(
            REFGUID guidKey,
            LPWSTR *ppwszValue,
            UINT32 *pcchLength)
        {
            return m_attributes->GetAllocatedString(guidKey, ppwszValue, pcchLength);
        }

        STDMETHODIMP GetBlobSize(
            REFGUID guidKey,
            UINT32 *pcbBlobSize)
        {
            return m_attributes->GetBlobSize(guidKey, pcbBlobSize);
        }

        STDMETHODIMP GetBlob(
            REFGUID guidKey,
            UINT8 *pBuf,
            UINT32 cbBufSize,
            UINT32 *pcbBlobSize)
        {
            return m_attributes->GetBlob(guidKey, pBuf, cbBufSize, pcbBlobSize);
        }

        STDMETHODIMP GetAllocatedBlob(
            REFGUID guidKey,
            UINT8 **ppBuf,
            UINT32 *pcbSize)
        {
            return m_attributes->GetAllocatedBlob(guidKey, ppBuf, pcbSize);
        }

        STDMETHODIMP GetUnknown(
            REFGUID guidKey,
            REFIID riid,
            LPVOID *ppv)
        {
            return m_attributes->GetUnknown(guidKey, riid, ppv);
        }

        STDMETHODIMP SetItem(
            REFGUID guidKey,
            REFPROPVARIANT Value)
        {
            return m_attributes->SetItem(guidKey, Value);
        }

        STDMETHODIMP DeleteItem(
            REFGUID guidKey)
        {
            return m_attributes->DeleteItem(guidKey);
        }

        STDMETHODIMP DeleteAllItems(void)
        {
            return m_attributes->DeleteAllItems();
        }

        STDMETHODIMP SetUINT32(
            REFGUID guidKey,
            UINT32 unValue)
        {
            return m_attributes->SetUINT32(guidKey, unValue);
        }

        STDMETHODIMP SetUINT64(
            REFGUID guidKey,
            UINT64 unValue)
        {
            return m_attributes->SetUINT64(guidKey, unValue);
        }

        STDMETHODIMP SetDouble(
            REFGUID guidKey,
            double fValue)
         {
            return m_attributes->SetDouble(guidKey, fValue);
        }

        STDMETHODIMP SetGUID(
            REFGUID guidKey,
            REFGUID guidValue)
        {
            return m_attributes->SetGUID(guidKey, guidValue);
        }

        STDMETHODIMP SetString(
            REFGUID guidKey,
            LPCWSTR wszValue)
        {
            return m_attributes->SetString(guidKey, wszValue);
        }

        STDMETHODIMP SetBlob(
            REFGUID guidKey,
            const UINT8 *pBuf,
            UINT32 cbBufSize)
        {
            return m_attributes->SetBlob(guidKey, pBuf, cbBufSize);
        }

        STDMETHODIMP SetUnknown(
            REFGUID guidKey,
            IUnknown *pUnknown)
        {
            return m_attributes->SetUnknown(guidKey, pUnknown);
        }

        STDMETHODIMP LockStore(void)
        {
            return m_attributes->LockStore();
        }

        STDMETHODIMP UnlockStore(void)
        {
            return m_attributes->UnlockStore();
        }

        STDMETHODIMP GetCount(
             UINT32 *pcItems)
        {
            return m_attributes->GetCount(pcItems);
        }

        STDMETHODIMP GetItemByIndex(
            UINT32 unIndex,
            GUID *pguidKey,
            PROPVARIANT *pValue)
        {
            return m_attributes->GetItemByIndex(unIndex, pguidKey, pValue);
        }

        STDMETHODIMP CopyAllItems(
           IMFAttributes *pDest)
        {
            return m_attributes->CopyAllItems(pDest);
        }

        /////////////////////////////////
        void setSurface(QAbstractVideoSurface *surface)
        {
            QMutexLocker locker(&m_mutex);
            if (m_surface == surface)
                return;

            m_surface = surface;

            if (!m_sink)
                return;
            m_sink->setSurface(m_surface);
        }

        void supportedFormatsChanged()
        {
            QMutexLocker locker(&m_mutex);
            if (!m_sink)
                return;
            m_sink->supportedFormatsChanged();
        }

        void present()
        {
            QMutexLocker locker(&m_mutex);
            if (!m_sink)
                return;
            m_sink->present();
        }

        MFTIME getTime()
        {
            if (m_sink)
                return m_sink->getTime();
            return 0;
        }

        float getPlayRate()
        {
            if (m_sink)
                return m_sink->getPlayRate();
            return 1;
        }

    private:
        long    m_cRef;
        bool    m_shutdown;
        MediaSink *m_sink;
        MFVideoRendererControl *m_rendererControl;
        IMFAttributes *m_attributes;
        QAbstractVideoSurface *m_surface;
        QMutex m_mutex;
    };
}

MFVideoRendererControl::MFVideoRendererControl(QObject *parent)
    : QVideoRendererControl(parent)
    , m_surface(0)
    , m_callback(0)
{
    m_currentActivate = new VideoRendererActivate(this);
}

MFVideoRendererControl::~MFVideoRendererControl()
{
    if (m_currentActivate) {
        m_currentActivate->ShutdownObject();
        m_currentActivate->Release();
    }
}

QAbstractVideoSurface *MFVideoRendererControl::surface() const
{
    return m_surface;
}

void MFVideoRendererControl::setSurface(QAbstractVideoSurface *surface)
{
    if (m_surface == surface)
        return;

    if (m_surface)
        disconnect(m_surface, SIGNAL(supportedFormatsChanged()), this, SLOT(supportedFormatsChanged()));
    m_surface = surface;

    if (m_surface) {
        connect(m_surface, SIGNAL(supportedFormatsChanged()), this, SLOT(supportedFormatsChanged()));
    }
    static_cast<VideoRendererActivate*>(m_currentActivate)->setSurface(m_surface);
}

void MFVideoRendererControl::customEvent(QEvent *event)
{
    if (event->type() == MediaStream::PresentSurface) {
        MFTIME targetTime = static_cast<MediaStream::PresentEvent*>(event)->targetTime();
        MFTIME currentTime = static_cast<VideoRendererActivate*>(m_currentActivate)->getTime();
        float playRate = static_cast<VideoRendererActivate*>(m_currentActivate)->getPlayRate();
        if (playRate > 0.0001f && targetTime > currentTime)
            QTimer::singleShot(int((float)((targetTime - currentTime) / 10000) / playRate), this, SLOT(present()));
        else
            present();
        return;
    }
    QChildEvent *childEvent = dynamic_cast<QChildEvent*>(event);
    if (!childEvent) {
        QObject::customEvent(event);
        return;
    }
    static_cast<MediaStream*>(childEvent->child())->customEvent(event);
}

void MFVideoRendererControl::supportedFormatsChanged()
{
    static_cast<VideoRendererActivate*>(m_currentActivate)->supportedFormatsChanged();
}

void MFVideoRendererControl::present()
{
    static_cast<VideoRendererActivate*>(m_currentActivate)->present();
}

IMFActivate* MFVideoRendererControl::currentActivate() const
{
    return m_currentActivate;
}

#include "moc_mfvideorenderercontrol.cpp"
#include "mfvideorenderercontrol.moc"
