summaryrefslogtreecommitdiffstats
path: root/src/plugins/wmf/player/mfvideorenderercontrol.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/wmf/player/mfvideorenderercontrol.cpp')
-rw-r--r--src/plugins/wmf/player/mfvideorenderercontrol.cpp2201
1 files changed, 2201 insertions, 0 deletions
diff --git a/src/plugins/wmf/player/mfvideorenderercontrol.cpp b/src/plugins/wmf/player/mfvideorenderercontrol.cpp
new file mode 100644
index 000000000..ef9ac7821
--- /dev/null
+++ b/src/plugins/wmf/player/mfvideorenderercontrol.cpp
@@ -0,0 +1,2201 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the Qt Mobility Components.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** 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, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $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 plugins/multimedia/wmf/player/mfstream.cpp
+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"