/**************************************************************************** ** ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: http://www.qt-project.org/ ** ** This file is part of the Qt Mobility Components. ** ** $QT_BEGIN_LICENSE:LGPL$ ** GNU Lesser General Public License Usage ** 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. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU General ** Public License version 3.0 as published by the Free Software Foundation ** and appearing in the file LICENSE.GPL included in the packaging of this ** file. Please review the following information to ensure the GNU General ** Public License version 3.0 requirements will be met: ** http://www.gnu.org/copyleft/gpl.html. ** ** Other Usage ** Alternatively, this file may be used in accordance with the terms and ** conditions contained in a signed written agreement between you and Nokia. ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "mfvideorenderercontrol.h" #include #include #include #include #include #include #include #include #include "guiddef.h" #include //#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(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 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(static_cast(this)); } else if (iid == __uuidof(IMFAsyncCallback)) { *ppv = static_cast(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(this); } else if (iid == __uuidof(IMarker)) { *ppv = static_cast(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(this); } else if (riid == IID_IMFMediaEventGenerator) { *ppvObject = static_cast(this); } else if (riid == IID_IMFMediaTypeHandler) { *ppvObject = static_cast(this); } else if (riid == IID_IUnknown) { *ppvObject = static_cast(static_cast(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 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(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 m_workQueueCB; QList m_sampleQueue; IMFAsyncResult *m_finalizeResult; // Result object for Finalize operation. MFTIME m_startTime; // Presentation time when the clock started. bool m_shutdown; QList m_mediaTypes; QList 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::Iterator pos = m_sampleQueue.begin(); // Enumerate all of the samples/markers in the queue. while (pos != m_sampleQueue.end()) { IUnknown *pUnk = NULL; IMarker *pMarker = NULL; IMFSample *pSample = NULL; pUnk = *pos; // Figure out if this is a marker or a sample. if (SUCCEEDED(hr)) { hr = pUnk->QueryInterface(__uuidof(IMarker), (void**)&pMarker); if (hr == E_NOINTERFACE) hr = pUnk->QueryInterface(IID_IMFSample, (void**)&pSample); } // Now handle the sample/marker appropriately. if (SUCCEEDED(hr)) { if (pMarker) { hr = sendMarkerEvent(pMarker, bFlushData); } else { Q_ASSERT(pSample != NULL); // Not a marker, must be a sample if (bFlushData == WriteSamples) hr = processSampleData(pSample); } } if (pMarker) pMarker->Release(); if (pSample) pSample->Release(); if (FAILED(hr)) break; pos++; } clearSampleQueue(); return hr; } void beginPreroll() { if (m_prerolling) return; m_prerolling = true; clearSampleQueue(); clearBufferCache(); queueEvent(MEStreamSinkRequestSample, GUID_NULL, S_OK, NULL); } void endPreroll(HRESULT hrStatus) { if (!m_prerolling) return; m_prerolling = false; queueEvent(MEStreamSinkPrerolled, GUID_NULL, hrStatus, NULL); } MFTIME m_prerollTargetTime; bool m_prerolling; void clearSampleQueue() { foreach (IUnknown* sample, m_sampleQueue) sample->Release(); m_sampleQueue.clear(); } HRESULT sendMarkerEvent(IMarker *pMarker, FlushState FlushState) { HRESULT hr = S_OK; HRESULT hrStatus = S_OK; // Status code for marker event. if (FlushState == DropSamples) hrStatus = E_ABORT; PROPVARIANT var; PropVariantInit(&var); // Get the context data. hr = pMarker->GetContext(&var); if (SUCCEEDED(hr)) hr = queueEvent(MEStreamSinkMarker, GUID_NULL, hrStatus, &var); PropVariantClear(&var); return hr; } HRESULT dispatchProcessSample(AsyncOperation* pOp) { HRESULT hr = S_OK; Q_ASSERT(pOp != NULL); hr = processSamplesFromQueue(WriteSamples); // We are in the middle of an asynchronous operation, so if something failed, send an error. if (FAILED(hr)) hr = queueEvent(MEError, GUID_NULL, hr, NULL); return hr; } HRESULT dispatchFinalize(AsyncOperation*) { HRESULT hr = S_OK; // Write any samples left in the queue... hr = processSamplesFromQueue(WriteSamples); // Set the async status and invoke the callback. m_finalizeResult->SetStatus(hr); hr = MFInvokeCallback(m_finalizeResult); return hr; } HRESULT processSampleData(IMFSample *pSample) { LONGLONG time; HRESULT hr = pSample->GetSampleTime(&time); if (m_prerolling) { if (SUCCEEDED(hr) && time >= m_prerollTargetTime) { IMFMediaBuffer *pBuffer = NULL; hr = pSample->ConvertToContiguousBuffer(&pBuffer); if (SUCCEEDED(hr)) { SampleBuffer sb; sb.m_buffer = pBuffer; sb.m_time = time; m_bufferCache.push_back(sb); endPreroll(S_OK); } } else { queueEvent(MEStreamSinkRequestSample, GUID_NULL, S_OK, NULL); } } else { bool requestSample = true; // If the time stamp is too early, just discard this sample. if (SUCCEEDED(hr) && time >= m_startTime) { IMFMediaBuffer *pBuffer = NULL; hr = pSample->ConvertToContiguousBuffer(&pBuffer); if (SUCCEEDED(hr)) { SampleBuffer sb; sb.m_buffer = pBuffer; sb.m_time = time; m_bufferCache.push_back(sb); } if (m_rate == 0) requestSample = false; } schedulePresentation(requestSample); } return hr; } class SampleBuffer { public: IMFMediaBuffer *m_buffer; LONGLONG m_time; }; QList 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(this); } else if (riid == IID_IMFMediaSinkPreroll) { *ppvObject = static_cast(this); } else if (riid == IID_IMFClockStateSink) { *ppvObject = static_cast(this); } else if (riid == IID_IUnknown) { *ppvObject = static_cast(static_cast(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(this); } else if (riid == IID_IMFAttributes) { *ppvObject = static_cast(this); } else if (riid == IID_IUnknown) { *ppvObject = static_cast(static_cast(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(m_currentActivate)->setSurface(m_surface); } void MFVideoRendererControl::customEvent(QEvent *event) { if (event->type() == MediaStream::PresentSurface) { MFTIME targetTime = static_cast(event)->targetTime(); MFTIME currentTime = static_cast(m_currentActivate)->getTime(); float playRate = static_cast(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(event); if (!childEvent) { QObject::customEvent(event); return; } static_cast(childEvent->child())->customEvent(event); } void MFVideoRendererControl::supportedFormatsChanged() { static_cast(m_currentActivate)->supportedFormatsChanged(); } void MFVideoRendererControl::present() { static_cast(m_currentActivate)->present(); } IMFActivate* MFVideoRendererControl::currentActivate() const { return m_currentActivate; } #include "moc_mfvideorenderercontrol.cpp" #include "mfvideorenderercontrol.moc"