blob: 94d5f68bee4b6e6ec8f4fbb2047067a655ef6874 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** 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 The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/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 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "mfvideorenderercontrol.h"
#include "mfactivate.h"
#include "evrcustompresenter.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>
#include <QtMultimedia/private/qmediaopenglhelper_p.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;
};
// 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_bufferStartTime(-1)
, m_bufferDuration(-1)
, m_presentationClock(0)
, m_sampleRequested(false)
, 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;
MFVideoArea viewport;
if (SUCCEEDED(pMediaType->GetBlob(MF_MT_GEOMETRIC_APERTURE,
reinterpret_cast<UINT8*>(&viewport),
sizeof(MFVideoArea),
NULL))) {
m_surfaceFormat.setViewport(QRect(viewport.OffsetX.value,
viewport.OffsetY.value,
viewport.Area.cx,
viewport.Area.cy));
}
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;
}
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_state = State_Prerolling;
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);
HRESULT hr = validateOperation(OpSetRate);
if (SUCCEEDED(hr)) {
m_rate = rate;
hr = queueAsyncOperation(OpSetRate);
}
return hr;
}
void supportedFormatsChanged()
{
QMutexLocker locker(&m_mutex);
m_pixelFormats.clear();
clearMediaTypes();
if (!m_surface)
return;
const QList<QVideoFrame::PixelFormat> formats = m_surface->supportedPixelFormats();
for (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_BGR24: // MFVideoFormat_RGB24 has a BGR layout
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;
}
// QAbstractVideoSurface::supportedPixelFormats() returns formats in descending
// order of preference, while IMFMediaTypeHandler is supposed to return supported
// formats in ascending order of preference. We need to reverse the list.
m_pixelFormats.prepend(format);
m_mediaTypes.prepend(mediaType);
}
}
void present()
{
QMutexLocker locker(&m_mutex);
if (!m_scheduledBuffer)
return;
QVideoFrame frame = QVideoFrame(
new MediaSampleVideoBuffer(m_scheduledBuffer, m_bytesPerLine),
m_surfaceFormat.frameSize(),
m_surfaceFormat.pixelFormat());
frame.setStartTime(m_bufferStartTime * 0.1);
frame.setEndTime((m_bufferStartTime + m_bufferDuration) * 0.1);
m_surface->present(frame);
m_scheduledBuffer->Release();
m_scheduledBuffer = NULL;
if (m_rate != 0)
schedulePresentation(true);
}
void clearScheduledFrame()
{
QMutexLocker locker(&m_mutex);
if (m_scheduledBuffer) {
m_scheduledBuffer->Release();
m_scheduledBuffer = NULL;
schedulePresentation(true);
}
}
enum
{
StartSurface = QEvent::User,
StopSurface,
FlushSurface,
PresentSurface
};
class PresentEvent : public QEvent
{
public:
PresentEvent(MFTIME targetTime)
: QEvent(QEvent::Type(PresentSurface))
, m_time(targetTime)
{
}
MFTIME 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_Prerolling,
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()
{
for (IMFMediaType* mediaType : qAsConst(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:
case QVideoFrame::Format_BGR24:
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);
if (m_shutdown)
return MF_E_SHUTDOWN;
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;
}
}
if (m_state == State_Started)
schedulePresentation(true);
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);
clearBufferCache();
// 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:
hr = queueEvent(MEStreamSinkRateChanged, GUID_NULL, S_OK, NULL);
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() {
for (IUnknown* sample : qAsConst(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);
Q_UNUSED(pOp)
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)
{
m_sampleRequested = false;
LONGLONG time, duration = -1;
HRESULT hr = pSample->GetSampleTime(&time);
if (SUCCEEDED(hr))
pSample->GetSampleDuration(&duration);
if (m_prerolling) {
if (SUCCEEDED(hr) && ((time - m_prerollTargetTime) * m_rate) >= 0) {
IMFMediaBuffer *pBuffer = NULL;
hr = pSample->ConvertToContiguousBuffer(&pBuffer);
if (SUCCEEDED(hr)) {
SampleBuffer sb;
sb.m_buffer = pBuffer;
sb.m_time = time;
sb.m_duration = duration;
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) * m_rate) >= 0) {
IMFMediaBuffer *pBuffer = NULL;
hr = pSample->ConvertToContiguousBuffer(&pBuffer);
if (SUCCEEDED(hr)) {
SampleBuffer sb;
sb.m_buffer = pBuffer;
sb.m_time = time;
sb.m_duration = duration;
m_bufferCache.push_back(sb);
}
if (m_rate == 0)
requestSample = false;
}
schedulePresentation(requestSample);
}
return hr;
}
class SampleBuffer
{
public:
IMFMediaBuffer *m_buffer;
LONGLONG m_time;
LONGLONG m_duration;
};
QList<SampleBuffer> m_bufferCache;
static const int BUFFER_CACHE_SIZE = 2;
void clearBufferCache()
{
for (SampleBuffer sb : qAsConst(m_bufferCache))
sb.m_buffer->Release();
m_bufferCache.clear();
if (m_scheduledBuffer) {
m_scheduledBuffer->Release();
m_scheduledBuffer = NULL;
}
}
void schedulePresentation(bool requestSample)
{
if (m_state == State_Paused || m_state == State_Prerolling)
return;
if (!m_scheduledBuffer) {
//get time from presentation time
MFTIME currentTime = m_startTime, sysTime;
bool timeOK = true;
if (m_rate != 0) {
if (FAILED(m_presentationClock->GetCorrelatedTime(0, &currentTime, &sysTime)))
timeOK = false;
}
while (!m_bufferCache.isEmpty()) {
SampleBuffer sb = m_bufferCache.takeFirst();
if (timeOK && ((sb.m_time - currentTime) * m_rate) < 0) {
sb.m_buffer->Release();
#ifdef DEBUG_MEDIAFOUNDATION
qDebug() << "currentPresentTime =" << float(currentTime / 10000) * 0.001f << " and sampleTime is" << float(sb.m_time / 10000) * 0.001f;
#endif
continue;
}
m_scheduledBuffer = sb.m_buffer;
m_bufferStartTime = sb.m_time;
m_bufferDuration = sb.m_duration;
QCoreApplication::postEvent(m_rendererControl, new PresentEvent(sb.m_time));
if (m_rate == 0)
queueEvent(MEStreamSinkScrubSampleComplete, GUID_NULL, S_OK, NULL);
break;
}
}
if (requestSample && !m_sampleRequested && m_bufferCache.size() < BUFFER_CACHE_SIZE) {
m_sampleRequested = true;
queueEvent(MEStreamSinkRequestSample, GUID_NULL, S_OK, NULL);
}
}
IMFMediaBuffer *m_scheduledBuffer;
MFTIME m_bufferStartTime;
MFTIME m_bufferDuration;
IMFPresentationClock *m_presentationClock;
bool m_sampleRequested;
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,
/* Prerolling */ TRUE, TRUE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, 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 IMFGetService,
public IMFRateSupport
{
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();
}
void clearScheduledFrame()
{
QMutexLocker locker(&m_mutex);
if (m_shutdown)
return;
m_stream->clearScheduledFrame();
}
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_IMFGetService) {
*ppvObject = static_cast<IMFGetService*>(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_IMFRateSupport) {
*ppvObject = static_cast<IMFRateSupport*>(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;
}
// IMFGetService methods
STDMETHODIMP GetService(const GUID &guidService,
const IID &riid,
LPVOID *ppvObject)
{
if (!ppvObject)
return E_POINTER;
if (guidService != MF_RATE_CONTROL_SERVICE)
return MF_E_UNSUPPORTED_SERVICE;
return QueryInterface(riid, ppvObject);
}
//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);
}
// IMFRateSupport methods
STDMETHODIMP GetFastestRate(MFRATE_DIRECTION eDirection,
BOOL fThin,
float *pflRate)
{
if (!pflRate)
return E_POINTER;
*pflRate = (fThin ? 8.f : 2.0f) * (eDirection == MFRATE_FORWARD ? 1 : -1) ;
return S_OK;
}
STDMETHODIMP GetSlowestRate(MFRATE_DIRECTION eDirection,
BOOL fThin,
float *pflRate)
{
Q_UNUSED(eDirection);
Q_UNUSED(fThin);
if (!pflRate)
return E_POINTER;
// we support any rate
*pflRate = 0.f;
return S_OK;
}
STDMETHODIMP IsRateSupported(BOOL fThin,
float flRate,
float *pflNearestSupportedRate)
{
HRESULT hr = S_OK;
if (!qFuzzyIsNull(flRate)) {
MFRATE_DIRECTION direction = flRate > 0.f ? MFRATE_FORWARD
: MFRATE_REVERSE;
float fastestRate = 0.f;
float slowestRate = 0.f;
GetFastestRate(direction, fThin, &fastestRate);
GetSlowestRate(direction, fThin, &slowestRate);
if (direction == MFRATE_REVERSE)
qSwap(fastestRate, slowestRate);
if (flRate < slowestRate || flRate > fastestRate) {
hr = MF_E_UNSUPPORTED_RATE;
if (pflNearestSupportedRate) {
*pflNearestSupportedRate = qBound(slowestRate,
flRate,
fastestRate);
}
}
} else if (pflNearestSupportedRate) {
*pflNearestSupportedRate = flRate;
}
return hr;
}
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();
}
void clearScheduledFrame()
{
QMutexLocker locker(&m_mutex);
if (!m_sink)
return;
m_sink->clearScheduledFrame();
}
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;
};
}
class EVRCustomPresenterActivate : public MFAbstractActivate
{
public:
EVRCustomPresenterActivate();
~EVRCustomPresenterActivate()
{ }
STDMETHODIMP ActivateObject(REFIID riid, void **ppv);
STDMETHODIMP ShutdownObject();
STDMETHODIMP DetachObject();
void setSurface(QAbstractVideoSurface *surface);
private:
EVRCustomPresenter *m_presenter;
QAbstractVideoSurface *m_surface;
QMutex m_mutex;
};
MFVideoRendererControl::MFVideoRendererControl(QObject *parent)
: QVideoRendererControl(parent)
, m_surface(0)
, m_currentActivate(0)
, m_callback(0)
, m_presenterActivate(0)
{
}
MFVideoRendererControl::~MFVideoRendererControl()
{
clear();
}
void MFVideoRendererControl::clear()
{
if (m_surface)
m_surface->stop();
if (m_presenterActivate) {
m_presenterActivate->ShutdownObject();
m_presenterActivate->Release();
m_presenterActivate = NULL;
}
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_presenterActivate)
m_presenterActivate->setSurface(m_surface);
else if (m_currentActivate)
static_cast<VideoRendererActivate*>(m_currentActivate)->setSurface(m_surface);
}
void MFVideoRendererControl::customEvent(QEvent *event)
{
if (m_presenterActivate)
return;
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 (!qFuzzyIsNull(playRate) && targetTime != currentTime) {
// If the scheduled frame is too late, skip it
const int interval = ((targetTime - currentTime) / 10000) / playRate;
if (interval < 0)
static_cast<VideoRendererActivate*>(m_currentActivate)->clearScheduledFrame();
else
QTimer::singleShot(interval, this, SLOT(present()));
} else {
present();
}
return;
}
if (event->type() >= MediaStream::StartSurface) {
QChildEvent *childEvent = static_cast<QChildEvent*>(event);
static_cast<MediaStream*>(childEvent->child())->customEvent(event);
} else {
QObject::customEvent(event);
}
}
void MFVideoRendererControl::supportedFormatsChanged()
{
if (m_presenterActivate)
return;
if (m_currentActivate)
static_cast<VideoRendererActivate*>(m_currentActivate)->supportedFormatsChanged();
}
void MFVideoRendererControl::present()
{
if (m_presenterActivate)
return;
if (m_currentActivate)
static_cast<VideoRendererActivate*>(m_currentActivate)->present();
}
IMFActivate* MFVideoRendererControl::createActivate()
{
Q_ASSERT(m_surface);
clear();
// Create the EVR media sink, but replace the presenter with our own
if (SUCCEEDED(MFCreateVideoRendererActivate(::GetShellWindow(), &m_currentActivate))) {
m_presenterActivate = new EVRCustomPresenterActivate;
m_currentActivate->SetUnknown(MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_ACTIVATE, m_presenterActivate);
} else {
m_currentActivate = new VideoRendererActivate(this);
}
setSurface(m_surface);
return m_currentActivate;
}
EVRCustomPresenterActivate::EVRCustomPresenterActivate()
: MFAbstractActivate()
, m_presenter(0)
, m_surface(0)
{ }
HRESULT EVRCustomPresenterActivate::ActivateObject(REFIID riid, void **ppv)
{
if (!ppv)
return E_INVALIDARG;
QMutexLocker locker(&m_mutex);
if (!m_presenter) {
m_presenter = new EVRCustomPresenter;
if (m_surface)
m_presenter->setSurface(m_surface);
}
return m_presenter->QueryInterface(riid, ppv);
}
HRESULT EVRCustomPresenterActivate::ShutdownObject()
{
// The presenter does not implement IMFShutdown so
// this function is the same as DetachObject()
return DetachObject();
}
HRESULT EVRCustomPresenterActivate::DetachObject()
{
QMutexLocker locker(&m_mutex);
if (m_presenter) {
m_presenter->Release();
m_presenter = 0;
}
return S_OK;
}
void EVRCustomPresenterActivate::setSurface(QAbstractVideoSurface *surface)
{
QMutexLocker locker(&m_mutex);
if (m_surface == surface)
return;
m_surface = surface;
if (m_presenter)
m_presenter->setSurface(surface);
}
#include "moc_mfvideorenderercontrol.cpp"
#include "mfvideorenderercontrol.moc"