Change-Id: Ia8c1c38aba1544603fada8c414cc856f365fd15b Reviewed-by: Akseli Salovaara <akseli.salovaara@digia.com> Reviewed-by: Sergio Ahumada <sergio.ahumada@digia.com>
2229 lines
69 KiB
C++
2229 lines
69 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
|
** Contact: http://www.qt-project.org/legal
|
|
**
|
|
** This file is part of the Qt Mobility Components.
|
|
**
|
|
** $QT_BEGIN_LICENSE:LGPL$
|
|
** Commercial License Usage
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
** accordance with the commercial license agreement provided with the
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
** a written agreement between you and Digia. For licensing terms and
|
|
** conditions see http://qt.digia.com/licensing. For further information
|
|
** use the contact form at http://qt.digia.com/contact-us.
|
|
**
|
|
** GNU Lesser General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
|
** General Public License version 2.1 as published by the Free Software
|
|
** Foundation and appearing in the file LICENSE.LGPL included in the
|
|
** packaging of this file. Please review the following information to
|
|
** ensure the GNU Lesser General Public License version 2.1 requirements
|
|
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
|
**
|
|
** In addition, as a special exception, Digia gives you certain additional
|
|
** rights. These rights are described in the Digia Qt LGPL Exception
|
|
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
|
**
|
|
** GNU General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
** General Public License version 3.0 as published by the Free Software
|
|
** Foundation and appearing in the file LICENSE.GPL included in the
|
|
** packaging of this file. Please review the following information to
|
|
** ensure the GNU General Public License version 3.0 requirements will be
|
|
** met: http://www.gnu.org/copyleft/gpl.html.
|
|
**
|
|
**
|
|
** $QT_END_LICENSE$
|
|
**
|
|
****************************************************************************/
|
|
|
|
#include "mfvideorenderercontrol.h"
|
|
#include <mferror.h>
|
|
#include <qabstractvideosurface.h>
|
|
#include <qvideosurfaceformat.h>
|
|
#include <qtcore/qtimer.h>
|
|
#include <qtcore/qmutex.h>
|
|
#include <qtcore/qcoreevent.h>
|
|
#include <qtcore/qcoreapplication.h>
|
|
#include <qtcore/qthread.h>
|
|
#include "guiddef.h"
|
|
#include <qtcore/qdebug.h>
|
|
|
|
//#define DEBUG_MEDIAFOUNDATION
|
|
#define PAD_TO_DWORD(x) (((x) + 3) & ~3)
|
|
|
|
namespace
|
|
{
|
|
class MediaSampleVideoBuffer : public QAbstractVideoBuffer
|
|
{
|
|
public:
|
|
MediaSampleVideoBuffer(IMFMediaBuffer *buffer, int bytesPerLine)
|
|
: QAbstractVideoBuffer(NoHandle)
|
|
, m_buffer(buffer)
|
|
, m_bytesPerLine(bytesPerLine)
|
|
, m_mapMode(NotMapped)
|
|
{
|
|
buffer->AddRef();
|
|
}
|
|
|
|
~MediaSampleVideoBuffer()
|
|
{
|
|
m_buffer->Release();
|
|
}
|
|
|
|
uchar *map(MapMode mode, int *numBytes, int *bytesPerLine)
|
|
{
|
|
if (m_mapMode == NotMapped && mode != NotMapped) {
|
|
BYTE *bytes;
|
|
DWORD length;
|
|
HRESULT hr = m_buffer->Lock(&bytes, NULL, &length);
|
|
if (SUCCEEDED(hr)) {
|
|
if (numBytes)
|
|
*numBytes = int(length);
|
|
|
|
if (bytesPerLine)
|
|
*bytesPerLine = m_bytesPerLine;
|
|
|
|
m_mapMode = mode;
|
|
return reinterpret_cast<uchar *>(bytes);
|
|
} else {
|
|
qWarning("Faild to lock mf buffer!");
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void unmap()
|
|
{
|
|
if (m_mapMode == NotMapped)
|
|
return;
|
|
m_mapMode = NotMapped;
|
|
m_buffer->Unlock();
|
|
}
|
|
|
|
MapMode mapMode() const
|
|
{
|
|
return m_mapMode;
|
|
}
|
|
|
|
private:
|
|
IMFMediaBuffer *m_buffer;
|
|
int m_bytesPerLine;
|
|
MapMode m_mapMode;
|
|
};
|
|
|
|
template<class T>
|
|
class AsyncCallback : public IMFAsyncCallback
|
|
{
|
|
public:
|
|
typedef HRESULT (T::*InvokeFn)(IMFAsyncResult *pAsyncResult);
|
|
|
|
AsyncCallback(T *pParent, InvokeFn fn) : m_pParent(pParent), m_pInvokeFn(fn)
|
|
{
|
|
}
|
|
|
|
// IUnknown
|
|
STDMETHODIMP QueryInterface(REFIID iid, void** ppv)
|
|
{
|
|
if (!ppv)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
if (iid == __uuidof(IUnknown))
|
|
{
|
|
*ppv = static_cast<IUnknown*>(static_cast<IMFAsyncCallback*>(this));
|
|
}
|
|
else if (iid == __uuidof(IMFAsyncCallback))
|
|
{
|
|
*ppv = static_cast<IMFAsyncCallback*>(this);
|
|
}
|
|
else
|
|
{
|
|
*ppv = NULL;
|
|
return E_NOINTERFACE;
|
|
}
|
|
AddRef();
|
|
return S_OK;
|
|
}
|
|
STDMETHODIMP_(ULONG) AddRef()
|
|
{
|
|
// Delegate to parent class.
|
|
return m_pParent->AddRef();
|
|
}
|
|
STDMETHODIMP_(ULONG) Release()
|
|
{
|
|
// Delegate to parent class.
|
|
return m_pParent->Release();
|
|
}
|
|
|
|
// IMFAsyncCallback methods
|
|
STDMETHODIMP GetParameters(DWORD*, DWORD*)
|
|
{
|
|
// Implementation of this method is optional.
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
STDMETHODIMP Invoke(IMFAsyncResult* pAsyncResult)
|
|
{
|
|
return (m_pParent->*m_pInvokeFn)(pAsyncResult);
|
|
}
|
|
|
|
T *m_pParent;
|
|
InvokeFn m_pInvokeFn;
|
|
};
|
|
|
|
|
|
// Custom interface for handling IMFStreamSink::PlaceMarker calls asynchronously.
|
|
MIDL_INTERFACE("a3ff32de-1031-438a-8b47-82f8acda59b7")
|
|
IMarker : public IUnknown
|
|
{
|
|
virtual STDMETHODIMP GetMarkerType(MFSTREAMSINK_MARKER_TYPE *pType) = 0;
|
|
virtual STDMETHODIMP GetMarkerValue(PROPVARIANT *pvar) = 0;
|
|
virtual STDMETHODIMP GetContext(PROPVARIANT *pvar) = 0;
|
|
};
|
|
|
|
class Marker : public IMarker
|
|
{
|
|
public:
|
|
static HRESULT Create(
|
|
MFSTREAMSINK_MARKER_TYPE eMarkerType,
|
|
const PROPVARIANT* pvarMarkerValue, // Can be NULL.
|
|
const PROPVARIANT* pvarContextValue, // Can be NULL.
|
|
IMarker **ppMarker)
|
|
{
|
|
if (ppMarker == NULL)
|
|
return E_POINTER;
|
|
|
|
HRESULT hr = S_OK;
|
|
Marker *pMarker = new Marker(eMarkerType);
|
|
if (pMarker == NULL)
|
|
hr = E_OUTOFMEMORY;
|
|
|
|
// Copy the marker data.
|
|
if (SUCCEEDED(hr) && pvarMarkerValue)
|
|
hr = PropVariantCopy(&pMarker->m_varMarkerValue, pvarMarkerValue);
|
|
|
|
if (SUCCEEDED(hr) && pvarContextValue)
|
|
hr = PropVariantCopy(&pMarker->m_varContextValue, pvarContextValue);
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
*ppMarker = pMarker;
|
|
(*ppMarker)->AddRef();
|
|
}
|
|
|
|
if (pMarker)
|
|
pMarker->Release();
|
|
|
|
return hr;
|
|
}
|
|
|
|
// IUnknown methods.
|
|
STDMETHODIMP QueryInterface(REFIID iid, void** ppv)
|
|
{
|
|
if (!ppv)
|
|
return E_POINTER;
|
|
if (iid == IID_IUnknown) {
|
|
*ppv = static_cast<IUnknown*>(this);
|
|
} else if (iid == __uuidof(IMarker)) {
|
|
*ppv = static_cast<IMarker*>(this);
|
|
} else {
|
|
*ppv = NULL;
|
|
return E_NOINTERFACE;
|
|
}
|
|
AddRef();
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) AddRef()
|
|
{
|
|
return InterlockedIncrement(&m_cRef);
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) Release()
|
|
{
|
|
LONG cRef = InterlockedDecrement(&m_cRef);
|
|
if (cRef == 0)
|
|
delete this;
|
|
// For thread safety, return a temporary variable.
|
|
return cRef;
|
|
}
|
|
|
|
STDMETHODIMP GetMarkerType(MFSTREAMSINK_MARKER_TYPE *pType)
|
|
{
|
|
if (pType == NULL)
|
|
return E_POINTER;
|
|
*pType = m_eMarkerType;
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP GetMarkerValue(PROPVARIANT *pvar)
|
|
{
|
|
if (pvar == NULL)
|
|
return E_POINTER;
|
|
return PropVariantCopy(pvar, &m_varMarkerValue);
|
|
}
|
|
|
|
STDMETHODIMP GetContext(PROPVARIANT *pvar)
|
|
{
|
|
if (pvar == NULL)
|
|
return E_POINTER;
|
|
return PropVariantCopy(pvar, &m_varContextValue);
|
|
}
|
|
|
|
protected:
|
|
MFSTREAMSINK_MARKER_TYPE m_eMarkerType;
|
|
PROPVARIANT m_varMarkerValue;
|
|
PROPVARIANT m_varContextValue;
|
|
|
|
private:
|
|
long m_cRef;
|
|
|
|
Marker(MFSTREAMSINK_MARKER_TYPE eMarkerType) : m_cRef(1), m_eMarkerType(eMarkerType)
|
|
{
|
|
PropVariantInit(&m_varMarkerValue);
|
|
PropVariantInit(&m_varContextValue);
|
|
}
|
|
|
|
virtual ~Marker()
|
|
{
|
|
PropVariantClear(&m_varMarkerValue);
|
|
PropVariantClear(&m_varContextValue);
|
|
}
|
|
};
|
|
|
|
class MediaStream : public QObject, public IMFStreamSink, public IMFMediaTypeHandler
|
|
{
|
|
Q_OBJECT
|
|
friend class MFVideoRendererControl;
|
|
public:
|
|
static const DWORD DEFAULT_MEDIA_STREAM_ID = 0x0;
|
|
|
|
MediaStream(IMFMediaSink *parent, MFVideoRendererControl *rendererControl)
|
|
: m_cRef(1)
|
|
, m_eventQueue(0)
|
|
, m_shutdown(false)
|
|
, m_surface(0)
|
|
, m_state(State_TypeNotSet)
|
|
, m_currentFormatIndex(-1)
|
|
, m_bytesPerLine(0)
|
|
, m_workQueueId(0)
|
|
, m_workQueueCB(this, &MediaStream::onDispatchWorkItem)
|
|
, m_finalizeResult(0)
|
|
, m_scheduledBuffer(0)
|
|
, m_presentationClock(0)
|
|
, m_currentMediaType(0)
|
|
, m_prerolling(false)
|
|
, m_prerollTargetTime(0)
|
|
, m_startTime(0)
|
|
, m_rendererControl(rendererControl)
|
|
, m_rate(1.f)
|
|
{
|
|
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();
|
|
if (!m_surface)
|
|
return;
|
|
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,
|
|
OpStart,
|
|
OpPreroll,
|
|
OpRestart,
|
|
OpPause,
|
|
OpStop,
|
|
OpSetRate,
|
|
OpProcessSample,
|
|
OpPlaceMarker,
|
|
OpFinalize,
|
|
|
|
Op_Count = OpFinalize + 1 // Number of operations
|
|
};
|
|
|
|
// AsyncOperation:
|
|
// Used to queue asynchronous operations. When we call MFPutWorkItem, we use this
|
|
// object for the callback state (pState). Then, when the callback is invoked,
|
|
// we can use the object to determine which asynchronous operation to perform.
|
|
class AsyncOperation : public IUnknown
|
|
{
|
|
public:
|
|
AsyncOperation(StreamOperation op)
|
|
:m_cRef(1), m_op(op)
|
|
{
|
|
}
|
|
|
|
StreamOperation m_op; // The operation to perform.
|
|
|
|
//from IUnknown
|
|
STDMETHODIMP QueryInterface(REFIID iid, void** ppv)
|
|
{
|
|
if (!ppv)
|
|
return E_POINTER;
|
|
if (iid == IID_IUnknown) {
|
|
*ppv = static_cast<IUnknown*>(this);
|
|
} else {
|
|
*ppv = NULL;
|
|
return E_NOINTERFACE;
|
|
}
|
|
AddRef();
|
|
return S_OK;
|
|
}
|
|
STDMETHODIMP_(ULONG) AddRef()
|
|
{
|
|
return InterlockedIncrement(&m_cRef);
|
|
}
|
|
STDMETHODIMP_(ULONG) Release()
|
|
{
|
|
ULONG uCount = InterlockedDecrement(&m_cRef);
|
|
if (uCount == 0)
|
|
delete this;
|
|
// For thread safety, return a temporary variable.
|
|
return uCount;
|
|
}
|
|
|
|
private:
|
|
long m_cRef;
|
|
virtual ~AsyncOperation()
|
|
{
|
|
Q_ASSERT(m_cRef == 0);
|
|
}
|
|
};
|
|
|
|
// ValidStateMatrix: Defines a look-up table that says which operations
|
|
// are valid from which states.
|
|
static BOOL ValidStateMatrix[State_Count][Op_Count];
|
|
|
|
long m_cRef;
|
|
QMutex m_mutex;
|
|
|
|
IMFMediaType *m_currentMediaType;
|
|
State m_state;
|
|
IMFMediaSink *m_sink;
|
|
IMFMediaEventQueue *m_eventQueue;
|
|
DWORD m_workQueueId;
|
|
AsyncCallback<MediaStream> m_workQueueCB;
|
|
QList<IUnknown*> m_sampleQueue;
|
|
IMFAsyncResult *m_finalizeResult; // Result object for Finalize operation.
|
|
MFTIME m_startTime; // Presentation time when the clock started.
|
|
|
|
bool m_shutdown;
|
|
QList<IMFMediaType*> m_mediaTypes;
|
|
QList<QVideoFrame::PixelFormat> m_pixelFormats;
|
|
int m_currentFormatIndex;
|
|
int m_bytesPerLine;
|
|
QVideoSurfaceFormat m_surfaceFormat;
|
|
QAbstractVideoSurface* m_surface;
|
|
MFVideoRendererControl *m_rendererControl;
|
|
|
|
void clearMediaTypes()
|
|
{
|
|
foreach (IMFMediaType* mediaType, m_mediaTypes)
|
|
mediaType->Release();
|
|
m_mediaTypes.clear();
|
|
}
|
|
|
|
int getMediaTypeIndex(IMFMediaType *mt)
|
|
{
|
|
GUID majorType;
|
|
if (FAILED(mt->GetMajorType(&majorType)))
|
|
return -1;
|
|
if (majorType != MFMediaType_Video)
|
|
return -1;
|
|
|
|
GUID subType;
|
|
if (FAILED(mt->GetGUID(MF_MT_SUBTYPE, &subType)))
|
|
return -1;
|
|
|
|
for (int index = 0; index < m_mediaTypes.size(); ++index) {
|
|
GUID st;
|
|
m_mediaTypes[index]->GetGUID(MF_MT_SUBTYPE, &st);
|
|
if (st == subType)
|
|
return index;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int getBytesPerLine(const QVideoSurfaceFormat &format)
|
|
{
|
|
switch (format.pixelFormat()) {
|
|
// 32 bpp packed formats.
|
|
case QVideoFrame::Format_RGB32:
|
|
case QVideoFrame::Format_AYUV444:
|
|
return format.frameWidth() * 4;
|
|
// 24 bpp packed formats.
|
|
case QVideoFrame::Format_RGB24:
|
|
return PAD_TO_DWORD(format.frameWidth() * 3);
|
|
// 16 bpp packed formats.
|
|
case QVideoFrame::Format_RGB565:
|
|
case QVideoFrame::Format_RGB555:
|
|
case QVideoFrame::Format_YUYV:
|
|
case QVideoFrame::Format_UYVY:
|
|
return PAD_TO_DWORD(format.frameWidth() * 2);
|
|
// Planar formats.
|
|
case QVideoFrame::Format_IMC1:
|
|
case QVideoFrame::Format_IMC2:
|
|
case QVideoFrame::Format_IMC3:
|
|
case QVideoFrame::Format_IMC4:
|
|
case QVideoFrame::Format_YV12:
|
|
case QVideoFrame::Format_NV12:
|
|
case QVideoFrame::Format_YUV420P:
|
|
return PAD_TO_DWORD(format.frameWidth());
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Callback for MFPutWorkItem.
|
|
HRESULT onDispatchWorkItem(IMFAsyncResult* pAsyncResult)
|
|
{
|
|
QMutexLocker locker(&m_mutex);
|
|
HRESULT hr = S_OK;
|
|
IUnknown *pState = NULL;
|
|
hr = pAsyncResult->GetState(&pState);
|
|
if (SUCCEEDED(hr)) {
|
|
// The state object is an AsncOperation object.
|
|
AsyncOperation *pOp = (AsyncOperation*)pState;
|
|
StreamOperation op = pOp->m_op;
|
|
switch (op) {
|
|
case OpStart:
|
|
endPreroll(S_FALSE);
|
|
if (m_state == State_WaitForSurfaceStart) {
|
|
hr = m_startResult;
|
|
m_state = State_Started;
|
|
} else if (!m_surface->isActive()) {
|
|
if (thread() == QThread::currentThread()) {
|
|
hr = startSurface();
|
|
}
|
|
else {
|
|
m_state = State_WaitForSurfaceStart;
|
|
QCoreApplication::postEvent(m_rendererControl, new QChildEvent(QEvent::Type(StartSurface), this));
|
|
break;
|
|
}
|
|
}
|
|
case OpRestart:
|
|
endPreroll(S_FALSE);
|
|
if (SUCCEEDED(hr)) {
|
|
// Send MEStreamSinkStarted.
|
|
hr = queueEvent(MEStreamSinkStarted, GUID_NULL, hr, NULL);
|
|
// Kick things off by requesting samples...
|
|
schedulePresentation(true);
|
|
// There might be samples queue from earlier (ie, while paused).
|
|
if (SUCCEEDED(hr))
|
|
hr = processSamplesFromQueue(WriteSamples);
|
|
}
|
|
break;
|
|
case OpPreroll:
|
|
beginPreroll();
|
|
break;
|
|
case OpStop:
|
|
// Drop samples from queue.
|
|
hr = processSamplesFromQueue(DropSamples);
|
|
if (m_scheduledBuffer) {
|
|
m_scheduledBuffer->Release();
|
|
m_scheduledBuffer = NULL;
|
|
}
|
|
// Send the event even if the previous call failed.
|
|
hr = queueEvent(MEStreamSinkStopped, GUID_NULL, hr, NULL);
|
|
if (m_surface->isActive()) {
|
|
if (thread() == QThread::currentThread()) {
|
|
stopSurface();
|
|
}
|
|
else {
|
|
QCoreApplication::postEvent(m_rendererControl, new QChildEvent(QEvent::Type(StopSurface), this));
|
|
}
|
|
}
|
|
break;
|
|
case OpPause:
|
|
hr = queueEvent(MEStreamSinkPaused, GUID_NULL, hr, NULL);
|
|
break;
|
|
case OpSetRate:
|
|
//TODO:
|
|
break;
|
|
case OpProcessSample:
|
|
case OpPlaceMarker:
|
|
hr = dispatchProcessSample(pOp);
|
|
break;
|
|
case OpFinalize:
|
|
endPreroll(S_FALSE);
|
|
hr = dispatchFinalize(pOp);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pState)
|
|
pState->Release();
|
|
return hr;
|
|
}
|
|
|
|
|
|
HRESULT queueEvent(MediaEventType met, REFGUID guidExtendedType, HRESULT hrStatus, const PROPVARIANT* pvValue)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
if (m_shutdown)
|
|
hr = MF_E_SHUTDOWN;
|
|
if (SUCCEEDED(hr))
|
|
hr = m_eventQueue->QueueEventParamVar(met, guidExtendedType, hrStatus, pvValue);
|
|
return hr;
|
|
}
|
|
|
|
HRESULT validateOperation(StreamOperation op)
|
|
{
|
|
Q_ASSERT(!m_shutdown);
|
|
if (ValidStateMatrix[m_state][op])
|
|
return S_OK;
|
|
else
|
|
return MF_E_INVALIDREQUEST;
|
|
}
|
|
|
|
HRESULT queueAsyncOperation(StreamOperation op)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
AsyncOperation *asyncOp = new AsyncOperation(op);
|
|
if (asyncOp == NULL)
|
|
hr = E_OUTOFMEMORY;
|
|
|
|
if (SUCCEEDED(hr))
|
|
hr = MFPutWorkItem(m_workQueueId, &m_workQueueCB, asyncOp);
|
|
|
|
if (asyncOp)
|
|
asyncOp->Release();
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT processSamplesFromQueue(FlushState bFlushData)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
QList<IUnknown*>::Iterator pos = m_sampleQueue.begin();
|
|
// Enumerate all of the samples/markers in the queue.
|
|
while (pos != m_sampleQueue.end()) {
|
|
IUnknown *pUnk = NULL;
|
|
IMarker *pMarker = NULL;
|
|
IMFSample *pSample = NULL;
|
|
pUnk = *pos;
|
|
// Figure out if this is a marker or a sample.
|
|
if (SUCCEEDED(hr)) {
|
|
hr = pUnk->QueryInterface(__uuidof(IMarker), (void**)&pMarker);
|
|
if (hr == E_NOINTERFACE)
|
|
hr = pUnk->QueryInterface(IID_IMFSample, (void**)&pSample);
|
|
}
|
|
|
|
// Now handle the sample/marker appropriately.
|
|
if (SUCCEEDED(hr)) {
|
|
if (pMarker) {
|
|
hr = sendMarkerEvent(pMarker, bFlushData);
|
|
} else {
|
|
Q_ASSERT(pSample != NULL); // Not a marker, must be a sample
|
|
if (bFlushData == WriteSamples)
|
|
hr = processSampleData(pSample);
|
|
}
|
|
}
|
|
if (pMarker)
|
|
pMarker->Release();
|
|
if (pSample)
|
|
pSample->Release();
|
|
|
|
if (FAILED(hr))
|
|
break;
|
|
|
|
pos++;
|
|
}
|
|
|
|
clearSampleQueue();
|
|
return hr;
|
|
}
|
|
|
|
void beginPreroll()
|
|
{
|
|
if (m_prerolling)
|
|
return;
|
|
m_prerolling = true;
|
|
clearSampleQueue();
|
|
clearBufferCache();
|
|
queueEvent(MEStreamSinkRequestSample, GUID_NULL, S_OK, NULL);
|
|
}
|
|
|
|
void endPreroll(HRESULT hrStatus)
|
|
{
|
|
if (!m_prerolling)
|
|
return;
|
|
m_prerolling = false;
|
|
queueEvent(MEStreamSinkPrerolled, GUID_NULL, hrStatus, NULL);
|
|
}
|
|
MFTIME m_prerollTargetTime;
|
|
bool m_prerolling;
|
|
|
|
void clearSampleQueue() {
|
|
foreach (IUnknown* sample, m_sampleQueue)
|
|
sample->Release();
|
|
m_sampleQueue.clear();
|
|
}
|
|
|
|
HRESULT sendMarkerEvent(IMarker *pMarker, FlushState FlushState)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
HRESULT hrStatus = S_OK; // Status code for marker event.
|
|
if (FlushState == DropSamples)
|
|
hrStatus = E_ABORT;
|
|
|
|
PROPVARIANT var;
|
|
PropVariantInit(&var);
|
|
|
|
// Get the context data.
|
|
hr = pMarker->GetContext(&var);
|
|
|
|
if (SUCCEEDED(hr))
|
|
hr = queueEvent(MEStreamSinkMarker, GUID_NULL, hrStatus, &var);
|
|
|
|
PropVariantClear(&var);
|
|
return hr;
|
|
}
|
|
|
|
HRESULT dispatchProcessSample(AsyncOperation* pOp)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
Q_ASSERT(pOp != NULL);
|
|
hr = processSamplesFromQueue(WriteSamples);
|
|
// We are in the middle of an asynchronous operation, so if something failed, send an error.
|
|
if (FAILED(hr))
|
|
hr = queueEvent(MEError, GUID_NULL, hr, NULL);
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT dispatchFinalize(AsyncOperation*)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
// Write any samples left in the queue...
|
|
hr = processSamplesFromQueue(WriteSamples);
|
|
|
|
// Set the async status and invoke the callback.
|
|
m_finalizeResult->SetStatus(hr);
|
|
hr = MFInvokeCallback(m_finalizeResult);
|
|
return hr;
|
|
}
|
|
|
|
HRESULT processSampleData(IMFSample *pSample)
|
|
{
|
|
LONGLONG time;
|
|
HRESULT hr = pSample->GetSampleTime(&time);
|
|
|
|
if (m_prerolling) {
|
|
if (SUCCEEDED(hr) && time >= m_prerollTargetTime) {
|
|
IMFMediaBuffer *pBuffer = NULL;
|
|
hr = pSample->ConvertToContiguousBuffer(&pBuffer);
|
|
if (SUCCEEDED(hr)) {
|
|
SampleBuffer sb;
|
|
sb.m_buffer = pBuffer;
|
|
sb.m_time = time;
|
|
m_bufferCache.push_back(sb);
|
|
endPreroll(S_OK);
|
|
}
|
|
} else {
|
|
queueEvent(MEStreamSinkRequestSample, GUID_NULL, S_OK, NULL);
|
|
}
|
|
} else {
|
|
bool requestSample = true;
|
|
// If the time stamp is too early, just discard this sample.
|
|
if (SUCCEEDED(hr) && time >= m_startTime) {
|
|
IMFMediaBuffer *pBuffer = NULL;
|
|
hr = pSample->ConvertToContiguousBuffer(&pBuffer);
|
|
if (SUCCEEDED(hr)) {
|
|
SampleBuffer sb;
|
|
sb.m_buffer = pBuffer;
|
|
sb.m_time = time;
|
|
m_bufferCache.push_back(sb);
|
|
}
|
|
if (m_rate == 0)
|
|
requestSample = false;
|
|
}
|
|
schedulePresentation(requestSample);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
class SampleBuffer
|
|
{
|
|
public:
|
|
IMFMediaBuffer *m_buffer;
|
|
LONGLONG m_time;
|
|
};
|
|
QList<SampleBuffer> m_bufferCache;
|
|
static const int BUFFER_CACHE_SIZE = 2;
|
|
|
|
void clearBufferCache()
|
|
{
|
|
foreach (SampleBuffer sb, m_bufferCache)
|
|
sb.m_buffer->Release();
|
|
m_bufferCache.clear();
|
|
}
|
|
|
|
void schedulePresentation(bool requestSample)
|
|
{
|
|
if (m_state == State_Paused)
|
|
return;
|
|
if (!m_scheduledBuffer) {
|
|
//get time from presentation time
|
|
MFTIME currentTime = m_startTime, sysTime;
|
|
bool timeOK = true;
|
|
if (m_rate != 0) {
|
|
if (FAILED(m_presentationClock->GetCorrelatedTime(0, ¤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, TRUE, 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)
|
|
, m_surface(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_currentActivate(0)
|
|
, m_callback(0)
|
|
{
|
|
}
|
|
|
|
MFVideoRendererControl::~MFVideoRendererControl()
|
|
{
|
|
clear();
|
|
}
|
|
|
|
void MFVideoRendererControl::clear()
|
|
{
|
|
if (m_surface)
|
|
m_surface->stop();
|
|
|
|
if (m_currentActivate) {
|
|
m_currentActivate->ShutdownObject();
|
|
m_currentActivate->Release();
|
|
}
|
|
m_currentActivate = NULL;
|
|
}
|
|
|
|
void MFVideoRendererControl::releaseActivate()
|
|
{
|
|
clear();
|
|
}
|
|
|
|
QAbstractVideoSurface *MFVideoRendererControl::surface() const
|
|
{
|
|
return m_surface;
|
|
}
|
|
|
|
void MFVideoRendererControl::setSurface(QAbstractVideoSurface *surface)
|
|
{
|
|
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()));
|
|
}
|
|
|
|
if (m_currentActivate)
|
|
static_cast<VideoRendererActivate*>(m_currentActivate)->setSurface(m_surface);
|
|
}
|
|
|
|
void MFVideoRendererControl::customEvent(QEvent *event)
|
|
{
|
|
if (!m_currentActivate)
|
|
return;
|
|
|
|
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()
|
|
{
|
|
if (m_currentActivate)
|
|
static_cast<VideoRendererActivate*>(m_currentActivate)->supportedFormatsChanged();
|
|
}
|
|
|
|
void MFVideoRendererControl::present()
|
|
{
|
|
if (m_currentActivate)
|
|
static_cast<VideoRendererActivate*>(m_currentActivate)->present();
|
|
}
|
|
|
|
IMFActivate* MFVideoRendererControl::createActivate()
|
|
{
|
|
clear();
|
|
|
|
m_currentActivate = new VideoRendererActivate(this);
|
|
if (m_surface)
|
|
setSurface(m_surface);
|
|
|
|
return m_currentActivate;
|
|
}
|
|
|
|
#include "moc_mfvideorenderercontrol.cpp"
|
|
#include "mfvideorenderercontrol.moc"
|