diff options
Diffstat (limited to 'src/plugins/wmf/player/mfvideorenderercontrol.cpp')
-rw-r--r-- | src/plugins/wmf/player/mfvideorenderercontrol.cpp | 2201 |
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, ¤tTime, &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" |