summaryrefslogtreecommitdiffstats
path: root/src/multimedia/platform/wmf
diff options
context:
space:
mode:
Diffstat (limited to 'src/multimedia/platform/wmf')
-rw-r--r--src/multimedia/platform/wmf/decoder/decoder.pri12
-rw-r--r--src/multimedia/platform/wmf/decoder/mfaudiodecodercontrol.cpp486
-rw-r--r--src/multimedia/platform/wmf/decoder/mfaudiodecodercontrol_p.h119
-rw-r--r--src/multimedia/platform/wmf/decoder/mfdecodersourcereader.cpp197
-rw-r--r--src/multimedia/platform/wmf/decoder/mfdecodersourcereader_p.h101
-rw-r--r--src/multimedia/platform/wmf/evr/evr.pri20
-rw-r--r--src/multimedia/platform/wmf/evr/evrcustompresenter.cpp2062
-rw-r--r--src/multimedia/platform/wmf/evr/evrcustompresenter_p.h388
-rw-r--r--src/multimedia/platform/wmf/evr/evrd3dpresentengine.cpp412
-rw-r--r--src/multimedia/platform/wmf/evr/evrd3dpresentengine_p.h163
-rw-r--r--src/multimedia/platform/wmf/evr/evrdefs.cpp48
-rw-r--r--src/multimedia/platform/wmf/evr/evrdefs_p.h364
-rw-r--r--src/multimedia/platform/wmf/evr/evrhelpers.cpp186
-rw-r--r--src/multimedia/platform/wmf/evr/evrhelpers_p.h112
-rw-r--r--src/multimedia/platform/wmf/evr/evrvideowindowcontrol.cpp350
-rw-r--r--src/multimedia/platform/wmf/evr/evrvideowindowcontrol_p.h120
-rw-r--r--src/multimedia/platform/wmf/mfstream.cpp361
-rw-r--r--src/multimedia/platform/wmf/mfstream_p.h159
-rw-r--r--src/multimedia/platform/wmf/player/mfactivate.cpp87
-rw-r--r--src/multimedia/platform/wmf/player/mfactivate_p.h223
-rw-r--r--src/multimedia/platform/wmf/player/mfaudioendpointcontrol.cpp180
-rw-r--r--src/multimedia/platform/wmf/player/mfaudioendpointcontrol_p.h93
-rw-r--r--src/multimedia/platform/wmf/player/mfaudioprobecontrol.cpp75
-rw-r--r--src/multimedia/platform/wmf/player/mfaudioprobecontrol_p.h77
-rw-r--r--src/multimedia/platform/wmf/player/mfevrvideowindowcontrol.cpp91
-rw-r--r--src/multimedia/platform/wmf/player/mfevrvideowindowcontrol_p.h74
-rw-r--r--src/multimedia/platform/wmf/player/mfmetadatacontrol.cpp431
-rw-r--r--src/multimedia/platform/wmf/player/mfmetadatacontrol_p.h83
-rw-r--r--src/multimedia/platform/wmf/player/mfplayercontrol.cpp316
-rw-r--r--src/multimedia/platform/wmf/player/mfplayercontrol_p.h136
-rw-r--r--src/multimedia/platform/wmf/player/mfplayerservice.cpp167
-rw-r--r--src/multimedia/platform/wmf/player/mfplayerservice_p.h98
-rw-r--r--src/multimedia/platform/wmf/player/mfplayersession.cpp1816
-rw-r--r--src/multimedia/platform/wmf/player/mfplayersession_p.h250
-rw-r--r--src/multimedia/platform/wmf/player/mftvideo.cpp753
-rw-r--r--src/multimedia/platform/wmf/player/mftvideo_p.h128
-rw-r--r--src/multimedia/platform/wmf/player/mfvideoprobecontrol.cpp54
-rw-r--r--src/multimedia/platform/wmf/player/mfvideoprobecontrol_p.h70
-rw-r--r--src/multimedia/platform/wmf/player/mfvideorenderercontrol.cpp2424
-rw-r--r--src/multimedia/platform/wmf/player/mfvideorenderercontrol_p.h92
-rw-r--r--src/multimedia/platform/wmf/player/player.pri32
-rw-r--r--src/multimedia/platform/wmf/player/samplegrabber.cpp173
-rw-r--r--src/multimedia/platform/wmf/player/samplegrabber_p.h107
-rw-r--r--src/multimedia/platform/wmf/sourceresolver.cpp325
-rw-r--r--src/multimedia/platform/wmf/sourceresolver_p.h115
-rw-r--r--src/multimedia/platform/wmf/wmf.pri21
-rw-r--r--src/multimedia/platform/wmf/wmfserviceplugin.cpp103
-rw-r--r--src/multimedia/platform/wmf/wmfserviceplugin_p.h76
48 files changed, 14330 insertions, 0 deletions
diff --git a/src/multimedia/platform/wmf/decoder/decoder.pri b/src/multimedia/platform/wmf/decoder/decoder.pri
new file mode 100644
index 000000000..93cc683d4
--- /dev/null
+++ b/src/multimedia/platform/wmf/decoder/decoder.pri
@@ -0,0 +1,12 @@
+INCLUDEPATH += $$PWD
+
+LIBS += -lmfreadwrite -lwmcodecdspuuid
+QMAKE_USE += wmf
+
+HEADERS += \
+ $$PWD/mfdecodersourcereader_p.h \
+ $$PWD/mfaudiodecodercontrol_p.h
+
+SOURCES += \
+ $$PWD/mfdecodersourcereader.cpp \
+ $$PWD/mfaudiodecodercontrol.cpp
diff --git a/src/multimedia/platform/wmf/decoder/mfaudiodecodercontrol.cpp b/src/multimedia/platform/wmf/decoder/mfaudiodecodercontrol.cpp
new file mode 100644
index 000000000..f41e05ed9
--- /dev/null
+++ b/src/multimedia/platform/wmf/decoder/mfaudiodecodercontrol.cpp
@@ -0,0 +1,486 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $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 "Wmcodecdsp.h"
+#include "mfaudiodecodercontrol_p.h"
+
+MFAudioDecoderControl::MFAudioDecoderControl(QObject *parent)
+ : QAudioDecoderControl(parent)
+ , m_decoderSourceReader(new MFDecoderSourceReader)
+ , m_sourceResolver(new SourceResolver)
+ , m_resampler(0)
+ , m_state(QAudioDecoder::StoppedState)
+ , m_device(0)
+ , m_mfInputStreamID(0)
+ , m_mfOutputStreamID(0)
+ , m_bufferReady(false)
+ , m_duration(0)
+ , m_position(0)
+ , m_loadingSource(false)
+ , m_mfOutputType(0)
+ , m_convertSample(0)
+ , m_sourceReady(false)
+ , m_resamplerDirty(false)
+{
+ CoCreateInstance(CLSID_CResamplerMediaObject, NULL, CLSCTX_INPROC_SERVER, IID_IMFTransform, (LPVOID*)(&m_resampler));
+ if (!m_resampler) {
+ qCritical("MFAudioDecoderControl: Failed to create resampler(CLSID_CResamplerMediaObject)!");
+ return;
+ }
+ m_resampler->AddInputStreams(1, &m_mfInputStreamID);
+
+ connect(m_sourceResolver, SIGNAL(mediaSourceReady()), this, SLOT(handleMediaSourceReady()));
+ connect(m_sourceResolver, SIGNAL(error(long)), this, SLOT(handleMediaSourceError(long)));
+ connect(m_decoderSourceReader, SIGNAL(finished()), this, SLOT(handleSourceFinished()));
+
+ QAudioFormat defaultFormat;
+ defaultFormat.setCodec("audio/x-raw");
+ setAudioFormat(defaultFormat);
+}
+
+MFAudioDecoderControl::~MFAudioDecoderControl()
+{
+ if (m_mfOutputType)
+ m_mfOutputType->Release();
+ m_decoderSourceReader->shutdown();
+ m_decoderSourceReader->Release();
+ m_sourceResolver->Release();
+ if (m_resampler)
+ m_resampler->Release();
+}
+
+QAudioDecoder::State MFAudioDecoderControl::state() const
+{
+ return m_state;
+}
+
+QString MFAudioDecoderControl::sourceFilename() const
+{
+ return m_sourceFilename;
+}
+
+void MFAudioDecoderControl::onSourceCleared()
+{
+ bool positionDirty = false;
+ bool durationDirty = false;
+ if (m_position != 0) {
+ m_position = 0;
+ positionDirty = true;
+ }
+ if (m_duration != 0) {
+ m_duration = 0;
+ durationDirty = true;
+ }
+ if (positionDirty)
+ emit positionChanged(m_position);
+ if (durationDirty)
+ emit durationChanged(m_duration);
+}
+
+void MFAudioDecoderControl::setSourceFilename(const QString &fileName)
+{
+ if (!m_device && m_sourceFilename == fileName)
+ return;
+ m_sourceReady = false;
+ m_sourceResolver->cancel();
+ m_decoderSourceReader->setSource(0, m_audioFormat);
+ m_device = 0;
+ m_sourceFilename = fileName;
+ if (!m_sourceFilename.isEmpty()) {
+ m_sourceResolver->shutdown();
+ QUrl url;
+ if (m_sourceFilename.startsWith(':'))
+ url = QUrl(QStringLiteral("qrc%1").arg(m_sourceFilename));
+ else
+ url = QUrl::fromLocalFile(m_sourceFilename);
+ m_sourceResolver->load(url, 0);
+ m_loadingSource = true;
+ } else {
+ onSourceCleared();
+ }
+ emit sourceChanged();
+}
+
+QIODevice* MFAudioDecoderControl::sourceDevice() const
+{
+ return m_device;
+}
+
+void MFAudioDecoderControl::setSourceDevice(QIODevice *device)
+{
+ if (m_device == device && m_sourceFilename.isEmpty())
+ return;
+ m_sourceReady = false;
+ m_sourceResolver->cancel();
+ m_decoderSourceReader->setSource(0, m_audioFormat);
+ m_sourceFilename.clear();
+ m_device = device;
+ if (m_device) {
+ m_sourceResolver->shutdown();
+ m_sourceResolver->load(QUrl(), m_device);
+ m_loadingSource = true;
+ } else {
+ onSourceCleared();
+ }
+ emit sourceChanged();
+}
+
+void MFAudioDecoderControl::updateResamplerOutputType()
+{
+ m_resamplerDirty = false;
+ if (m_audioFormat == m_sourceOutputFormat)
+ return;
+ HRESULT hr = m_resampler->SetOutputType(m_mfOutputStreamID, m_mfOutputType, 0);
+ if (SUCCEEDED(hr)) {
+ MFT_OUTPUT_STREAM_INFO streamInfo;
+ m_resampler->GetOutputStreamInfo(m_mfOutputStreamID, &streamInfo);
+ if ((streamInfo.dwFlags & (MFT_OUTPUT_STREAM_PROVIDES_SAMPLES | MFT_OUTPUT_STREAM_CAN_PROVIDE_SAMPLES)) == 0) {
+ //if resampler does not allocate output sample memory, we do it here
+ if (m_convertSample) {
+ m_convertSample->Release();
+ m_convertSample = 0;
+ }
+ if (SUCCEEDED(MFCreateSample(&m_convertSample))) {
+ IMFMediaBuffer *mbuf = 0;;
+ if (SUCCEEDED(MFCreateMemoryBuffer(streamInfo.cbSize, &mbuf))) {
+ m_convertSample->AddBuffer(mbuf);
+ mbuf->Release();
+ }
+ }
+ }
+ } else {
+ qWarning() << "MFAudioDecoderControl: failed to SetOutputType of resampler" << hr;
+ }
+}
+
+void MFAudioDecoderControl::handleMediaSourceReady()
+{
+ m_loadingSource = false;
+ m_sourceReady = true;
+ IMFMediaType *mediaType = m_decoderSourceReader->setSource(m_sourceResolver->mediaSource(), m_audioFormat);
+ m_sourceOutputFormat = QAudioFormat();
+
+ if (mediaType) {
+ m_sourceOutputFormat = m_audioFormat;
+ QAudioFormat af = m_audioFormat;
+
+ UINT32 val = 0;
+ if (SUCCEEDED(mediaType->GetUINT32(MF_MT_AUDIO_NUM_CHANNELS, &val))) {
+ m_sourceOutputFormat.setChannelCount(int(val));
+ }
+ if (SUCCEEDED(mediaType->GetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, &val))) {
+ m_sourceOutputFormat.setSampleRate(int(val));
+ }
+ if (SUCCEEDED(mediaType->GetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, &val))) {
+ m_sourceOutputFormat.setSampleSize(int(val));
+ }
+
+ GUID subType;
+ if (SUCCEEDED(mediaType->GetGUID(MF_MT_SUBTYPE, &subType))) {
+ if (subType == MFAudioFormat_Float) {
+ m_sourceOutputFormat.setSampleType(QAudioFormat::Float);
+ } else if (m_sourceOutputFormat.sampleSize() == 8) {
+ m_sourceOutputFormat.setSampleType(QAudioFormat::UnSignedInt);
+ } else {
+ m_sourceOutputFormat.setSampleType(QAudioFormat::SignedInt);
+ }
+ }
+ if (m_sourceOutputFormat.sampleType() != QAudioFormat::Float) {
+ m_sourceOutputFormat.setByteOrder(QAudioFormat::LittleEndian);
+ }
+
+ if (m_audioFormat.sampleType() != QAudioFormat::Float
+ && m_audioFormat.sampleType() != QAudioFormat::SignedInt) {
+ af.setSampleType(m_sourceOutputFormat.sampleType());
+ }
+ if (af.sampleType() == QAudioFormat::SignedInt) {
+ af.setByteOrder(QAudioFormat::LittleEndian);
+ }
+ if (m_audioFormat.channelCount() <= 0) {
+ af.setChannelCount(m_sourceOutputFormat.channelCount());
+ }
+ if (m_audioFormat.sampleRate() <= 0) {
+ af.setSampleRate(m_sourceOutputFormat.sampleRate());
+ }
+ if (m_audioFormat.sampleSize() <= 0) {
+ af.setSampleSize(m_sourceOutputFormat.sampleSize());
+ }
+ setAudioFormat(af);
+ }
+
+ if (m_sourceResolver->mediaSource()) {
+ if (mediaType && m_resampler) {
+ HRESULT hr = S_OK;
+ hr = m_resampler->SetInputType(m_mfInputStreamID, mediaType, 0);
+ if (SUCCEEDED(hr)) {
+ updateResamplerOutputType();
+ } else {
+ qWarning() << "MFAudioDecoderControl: failed to SetInputType of resampler" << hr;
+ }
+ }
+ IMFPresentationDescriptor *pd;
+ if (SUCCEEDED(m_sourceResolver->mediaSource()->CreatePresentationDescriptor(&pd))) {
+ UINT64 duration = 0;
+ pd->GetUINT64(MF_PD_DURATION, &duration);
+ pd->Release();
+ duration /= 10000;
+ if (m_duration != qint64(duration)) {
+ m_duration = qint64(duration);
+ emit durationChanged(m_duration);
+ }
+ }
+ if (m_state == QAudioDecoder::DecodingState) {
+ activatePipeline();
+ }
+ } else if (m_state != QAudioDecoder::StoppedState) {
+ m_state = QAudioDecoder::StoppedState;
+ emit stateChanged(m_state);
+ }
+}
+
+void MFAudioDecoderControl::handleMediaSourceError(long hr)
+{
+ Q_UNUSED(hr);
+ m_loadingSource = false;
+ m_decoderSourceReader->setSource(0, m_audioFormat);
+ if (m_state != QAudioDecoder::StoppedState) {
+ m_state = QAudioDecoder::StoppedState;
+ emit stateChanged(m_state);
+ }
+}
+
+void MFAudioDecoderControl::activatePipeline()
+{
+ Q_ASSERT(!m_bufferReady);
+ m_state = QAudioDecoder::DecodingState;
+ connect(m_decoderSourceReader, SIGNAL(sampleAdded()), this, SLOT(handleSampleAdded()));
+ if (m_resamplerDirty) {
+ updateResamplerOutputType();
+ }
+ m_decoderSourceReader->reset();
+ m_decoderSourceReader->readNextSample();
+ if (m_position != 0) {
+ m_position = 0;
+ emit positionChanged(0);
+ }
+}
+
+void MFAudioDecoderControl::start()
+{
+ if (m_state != QAudioDecoder::StoppedState)
+ return;
+
+ if (m_loadingSource) {
+ //deferred starting
+ m_state = QAudioDecoder::DecodingState;
+ emit stateChanged(m_state);
+ return;
+ }
+
+ if (!m_decoderSourceReader->mediaSource())
+ return;
+ activatePipeline();
+ emit stateChanged(m_state);
+}
+
+void MFAudioDecoderControl::stop()
+{
+ if (m_state == QAudioDecoder::StoppedState)
+ return;
+ m_state = QAudioDecoder::StoppedState;
+ disconnect(m_decoderSourceReader, SIGNAL(sampleAdded()), this, SLOT(handleSampleAdded()));
+ if (m_bufferReady) {
+ m_bufferReady = false;
+ emit bufferAvailableChanged(m_bufferReady);
+ }
+ emit stateChanged(m_state);
+}
+
+void MFAudioDecoderControl::handleSampleAdded()
+{
+ QList<IMFSample*> samples = m_decoderSourceReader->takeSamples();
+ Q_ASSERT(samples.count() > 0);
+ Q_ASSERT(!m_bufferReady);
+ Q_ASSERT(m_resampler);
+ LONGLONG sampleStartTime = 0;
+ IMFSample *firstSample = samples.first();
+ firstSample->GetSampleTime(&sampleStartTime);
+ QByteArray abuf;
+ if (m_sourceOutputFormat == m_audioFormat) {
+ //no need for resampling
+ for (IMFSample *s : qAsConst(samples)) {
+ IMFMediaBuffer *buffer;
+ s->ConvertToContiguousBuffer(&buffer);
+ DWORD bufLen = 0;
+ BYTE *buf = 0;
+ if (SUCCEEDED(buffer->Lock(&buf, NULL, &bufLen))) {
+ abuf.push_back(QByteArray(reinterpret_cast<char*>(buf), bufLen));
+ buffer->Unlock();
+ }
+ buffer->Release();
+ LONGLONG sampleTime = 0, sampleDuration = 0;
+ s->GetSampleTime(&sampleTime);
+ s->GetSampleDuration(&sampleDuration);
+ m_position = qint64(sampleTime + sampleDuration) / 10000;
+ s->Release();
+ }
+ } else {
+ for (IMFSample *s : qAsConst(samples)) {
+ HRESULT hr = m_resampler->ProcessInput(m_mfInputStreamID, s, 0);
+ if (SUCCEEDED(hr)) {
+ MFT_OUTPUT_DATA_BUFFER outputDataBuffer;
+ outputDataBuffer.dwStreamID = m_mfOutputStreamID;
+ while (true) {
+ outputDataBuffer.pEvents = 0;
+ outputDataBuffer.dwStatus = 0;
+ outputDataBuffer.pSample = m_convertSample;
+ DWORD status = 0;
+ if (SUCCEEDED(m_resampler->ProcessOutput(0, 1, &outputDataBuffer, &status))) {
+ IMFMediaBuffer *buffer;
+ outputDataBuffer.pSample->ConvertToContiguousBuffer(&buffer);
+ DWORD bufLen = 0;
+ BYTE *buf = 0;
+ if (SUCCEEDED(buffer->Lock(&buf, NULL, &bufLen))) {
+ abuf.push_back(QByteArray(reinterpret_cast<char*>(buf), bufLen));
+ buffer->Unlock();
+ }
+ buffer->Release();
+ } else {
+ break;
+ }
+ }
+ }
+ LONGLONG sampleTime = 0, sampleDuration = 0;
+ s->GetSampleTime(&sampleTime);
+ s->GetSampleDuration(&sampleDuration);
+ m_position = qint64(sampleTime + sampleDuration) / 10000;
+ s->Release();
+ }
+ }
+ // WMF uses 100-nanosecond units, QAudioDecoder uses milliseconds, QAudioBuffer uses microseconds...
+ m_cachedAudioBuffer = QAudioBuffer(abuf, m_audioFormat, qint64(sampleStartTime / 10));
+ m_bufferReady = true;
+ emit positionChanged(m_position);
+ emit bufferAvailableChanged(m_bufferReady);
+ emit bufferReady();
+}
+
+void MFAudioDecoderControl::handleSourceFinished()
+{
+ stop();
+ emit finished();
+}
+
+QAudioFormat MFAudioDecoderControl::audioFormat() const
+{
+ return m_audioFormat;
+}
+
+void MFAudioDecoderControl::setAudioFormat(const QAudioFormat &format)
+{
+ if (m_audioFormat == format || !m_resampler)
+ return;
+ if (format.codec() != QLatin1String("audio/x-wav") && format.codec() != QLatin1String("audio/x-raw")) {
+ qWarning("MFAudioDecoderControl does not accept non-pcm audio format!");
+ return;
+ }
+ m_audioFormat = format;
+
+ if (m_audioFormat.isValid()) {
+ IMFMediaType *mediaType = 0;
+ MFCreateMediaType(&mediaType);
+ mediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio);
+ if (format.sampleType() == QAudioFormat::Float) {
+ mediaType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_Float);
+ } else {
+ mediaType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM);
+ }
+
+ mediaType->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, UINT32(m_audioFormat.channelCount()));
+ mediaType->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, UINT32(m_audioFormat.sampleRate()));
+ UINT32 alignmentBlock = UINT32(m_audioFormat.channelCount() * m_audioFormat.sampleSize() / 8);
+ mediaType->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, alignmentBlock);
+ UINT32 avgBytesPerSec = UINT32(m_audioFormat.sampleRate() * m_audioFormat.sampleSize() / 8 * m_audioFormat.channelCount());
+ mediaType->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, avgBytesPerSec);
+ mediaType->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, UINT32(m_audioFormat.sampleSize()));
+ mediaType->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE);
+
+ if (m_mfOutputType)
+ m_mfOutputType->Release();
+ m_mfOutputType = mediaType;
+ } else {
+ if (m_mfOutputType)
+ m_mfOutputType->Release();
+ m_mfOutputType = NULL;
+ }
+
+ if (m_sourceReady && m_state == QAudioDecoder::StoppedState) {
+ updateResamplerOutputType();
+ } else {
+ m_resamplerDirty = true;
+ }
+
+ emit formatChanged(m_audioFormat);
+}
+
+QAudioBuffer MFAudioDecoderControl::read()
+{
+ if (!m_bufferReady)
+ return QAudioBuffer();
+ QAudioBuffer buffer = m_cachedAudioBuffer;
+ m_bufferReady = false;
+ emit bufferAvailableChanged(m_bufferReady);
+ m_decoderSourceReader->readNextSample();
+ return buffer;
+}
+
+bool MFAudioDecoderControl::bufferAvailable() const
+{
+ return m_bufferReady;
+}
+
+qint64 MFAudioDecoderControl::position() const
+{
+ return m_position;
+}
+
+qint64 MFAudioDecoderControl::duration() const
+{
+ return m_duration;
+}
diff --git a/src/multimedia/platform/wmf/decoder/mfaudiodecodercontrol_p.h b/src/multimedia/platform/wmf/decoder/mfaudiodecodercontrol_p.h
new file mode 100644
index 000000000..98a3b9d02
--- /dev/null
+++ b/src/multimedia/platform/wmf/decoder/mfaudiodecodercontrol_p.h
@@ -0,0 +1,119 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $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$
+**
+****************************************************************************/
+
+#ifndef MFAUDIODECODERCONTROL_H
+#define MFAUDIODECODERCONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qaudiodecodercontrol.h"
+#include "mfdecodersourcereader_p.h"
+#include "private/sourceresolver_p.h"
+
+QT_USE_NAMESPACE
+
+class MFAudioDecoderControl : public QAudioDecoderControl
+{
+ Q_OBJECT
+public:
+ MFAudioDecoderControl(QObject *parent = 0);
+ ~MFAudioDecoderControl();
+
+ QAudioDecoder::State state() const;
+
+ QString sourceFilename() const;
+ void setSourceFilename(const QString &fileName);
+
+ QIODevice* sourceDevice() const;
+ void setSourceDevice(QIODevice *device);
+
+ void start();
+ void stop();
+
+ QAudioFormat audioFormat() const;
+ void setAudioFormat(const QAudioFormat &format);
+
+ QAudioBuffer read();
+ bool bufferAvailable() const;
+
+ qint64 position() const;
+ qint64 duration() const;
+
+private Q_SLOTS:
+ void handleMediaSourceReady();
+ void handleMediaSourceError(long hr);
+ void handleSampleAdded();
+ void handleSourceFinished();
+
+private:
+ void updateResamplerOutputType();
+ void activatePipeline();
+ void onSourceCleared();
+
+ MFDecoderSourceReader *m_decoderSourceReader;
+ SourceResolver *m_sourceResolver;
+ IMFTransform *m_resampler;
+ QAudioDecoder::State m_state;
+ QString m_sourceFilename;
+ QIODevice *m_device;
+ QAudioFormat m_audioFormat;
+ DWORD m_mfInputStreamID;
+ DWORD m_mfOutputStreamID;
+ bool m_bufferReady;
+ QAudioBuffer m_cachedAudioBuffer;
+ qint64 m_duration;
+ qint64 m_position;
+ bool m_loadingSource;
+ IMFMediaType *m_mfOutputType;
+ IMFSample *m_convertSample;
+ QAudioFormat m_sourceOutputFormat;
+ bool m_sourceReady;
+ bool m_resamplerDirty;
+};
+
+#endif//MFAUDIODECODERCONTROL_H
diff --git a/src/multimedia/platform/wmf/decoder/mfdecodersourcereader.cpp b/src/multimedia/platform/wmf/decoder/mfdecodersourcereader.cpp
new file mode 100644
index 000000000..b2b9cf60d
--- /dev/null
+++ b/src/multimedia/platform/wmf/decoder/mfdecodersourcereader.cpp
@@ -0,0 +1,197 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $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 "mfdecodersourcereader_p.h"
+
+MFDecoderSourceReader::MFDecoderSourceReader(QObject *parent)
+ : m_cRef(1)
+ , m_sourceReader(0)
+ , m_source(0)
+{
+ Q_UNUSED(parent);
+}
+
+void MFDecoderSourceReader::shutdown()
+{
+ if (m_source) {
+ m_source->Release();
+ m_source = NULL;
+ }
+ if (m_sourceReader) {
+ m_sourceReader->Release();
+ m_sourceReader = NULL;
+ }
+}
+
+IMFMediaSource* MFDecoderSourceReader::mediaSource()
+{
+ return m_source;
+}
+
+IMFMediaType* MFDecoderSourceReader::setSource(IMFMediaSource *source, const QAudioFormat &audioFormat)
+{
+ IMFMediaType *mediaType = NULL;
+ if (m_source == source)
+ return mediaType;
+ if (m_source) {
+ m_source->Release();
+ m_source = NULL;
+ }
+ if (m_sourceReader) {
+ m_sourceReader->Release();
+ m_sourceReader = NULL;
+ }
+ if (!source)
+ return mediaType;
+ IMFAttributes *attr = NULL;
+ MFCreateAttributes(&attr, 1);
+ if (SUCCEEDED(attr->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, this))) {
+ if (SUCCEEDED(MFCreateSourceReaderFromMediaSource(source, attr, &m_sourceReader))) {
+ m_source = source;
+ m_source->AddRef();
+ m_sourceReader->SetStreamSelection(DWORD(MF_SOURCE_READER_ALL_STREAMS), FALSE);
+ m_sourceReader->SetStreamSelection(DWORD(MF_SOURCE_READER_FIRST_AUDIO_STREAM), TRUE);
+ IMFMediaType *pPartialType = NULL;
+ MFCreateMediaType(&pPartialType);
+ pPartialType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio);
+
+ if (audioFormat.sampleType() == QAudioFormat::Float) {
+ pPartialType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_Float);
+ } else {
+ pPartialType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM);
+ }
+
+ m_sourceReader->SetCurrentMediaType(DWORD(MF_SOURCE_READER_FIRST_AUDIO_STREAM), NULL, pPartialType);
+ pPartialType->Release();
+ m_sourceReader->GetCurrentMediaType(DWORD(MF_SOURCE_READER_FIRST_AUDIO_STREAM), &mediaType);
+ // Ensure the stream is selected.
+ m_sourceReader->SetStreamSelection(DWORD(MF_SOURCE_READER_FIRST_AUDIO_STREAM), TRUE);
+ }
+ attr->Release();
+ }
+ return mediaType;
+}
+
+void MFDecoderSourceReader::reset()
+{
+ if (!m_sourceReader)
+ return;
+ PROPVARIANT vPos;
+ PropVariantInit(&vPos);
+ vPos.vt = VT_I8;
+ vPos.uhVal.QuadPart = 0;
+ m_sourceReader->SetCurrentPosition(GUID_NULL, vPos);
+}
+
+void MFDecoderSourceReader::readNextSample()
+{
+ if (!m_sourceReader)
+ return;
+ m_sourceReader->ReadSample(MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, NULL, NULL, NULL, NULL);
+}
+
+QList<IMFSample*> MFDecoderSourceReader::takeSamples() //internal samples will be cleared after this
+{
+ QList<IMFSample*> samples;
+ m_samplesMutex.lock();
+ samples = m_cachedSamples;
+ m_cachedSamples.clear();
+ m_samplesMutex.unlock();
+ return samples;
+}
+
+//from IUnknown
+STDMETHODIMP MFDecoderSourceReader::QueryInterface(REFIID riid, LPVOID *ppvObject)
+{
+ if (!ppvObject)
+ return E_POINTER;
+ if (riid == IID_IMFSourceReaderCallback) {
+ *ppvObject = static_cast<IMFSourceReaderCallback*>(this);
+ } else if (riid == IID_IUnknown) {
+ *ppvObject = static_cast<IUnknown*>(this);
+ } else {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+ AddRef();
+ return S_OK;
+}
+
+STDMETHODIMP_(ULONG) MFDecoderSourceReader::AddRef(void)
+{
+ return InterlockedIncrement(&m_cRef);
+}
+
+STDMETHODIMP_(ULONG) MFDecoderSourceReader::Release(void)
+{
+ LONG cRef = InterlockedDecrement(&m_cRef);
+ if (cRef == 0) {
+ this->deleteLater();
+ }
+ return cRef;
+}
+
+//from IMFSourceReaderCallback
+STDMETHODIMP MFDecoderSourceReader::OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex,
+ DWORD dwStreamFlags, LONGLONG llTimestamp, IMFSample *pSample)
+{
+ Q_UNUSED(hrStatus);
+ Q_UNUSED(dwStreamIndex);
+ Q_UNUSED(llTimestamp);
+ if (pSample) {
+ pSample->AddRef();
+ m_samplesMutex.lock();
+ m_cachedSamples.push_back(pSample);
+ m_samplesMutex.unlock();
+ emit sampleAdded();
+ } else if ((dwStreamFlags & MF_SOURCE_READERF_ENDOFSTREAM) == MF_SOURCE_READERF_ENDOFSTREAM) {
+ emit finished();
+ }
+ return S_OK;
+}
+
+STDMETHODIMP MFDecoderSourceReader::OnFlush(DWORD)
+{
+ return S_OK;
+}
+
+STDMETHODIMP MFDecoderSourceReader::OnEvent(DWORD, IMFMediaEvent*)
+{
+ return S_OK;
+}
diff --git a/src/multimedia/platform/wmf/decoder/mfdecodersourcereader_p.h b/src/multimedia/platform/wmf/decoder/mfdecodersourcereader_p.h
new file mode 100644
index 000000000..7d63f5368
--- /dev/null
+++ b/src/multimedia/platform/wmf/decoder/mfdecodersourcereader_p.h
@@ -0,0 +1,101 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef MFDECODERSOURCEREADER_H
+#define MFDECODERSOURCEREADER_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <mfapi.h>
+#include <mfidl.h>
+#include <Mfreadwrite.h>
+
+#include <QtCore/qobject.h>
+#include <QtCore/qmutex.h>
+#include "qaudioformat.h"
+
+QT_USE_NAMESPACE
+
+class MFDecoderSourceReader : public QObject, public IMFSourceReaderCallback
+{
+ Q_OBJECT
+public:
+ MFDecoderSourceReader(QObject *parent = 0);
+ void shutdown();
+
+ IMFMediaSource* mediaSource();
+ IMFMediaType* setSource(IMFMediaSource *source, const QAudioFormat &audioFormat);
+
+ void reset();
+ void readNextSample();
+ QList<IMFSample*> takeSamples(); //internal samples will be cleared after this
+
+ //from IUnknown
+ STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject);
+ STDMETHODIMP_(ULONG) AddRef(void);
+ STDMETHODIMP_(ULONG) Release(void);
+
+ //from IMFSourceReaderCallback
+ STDMETHODIMP OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex,
+ DWORD dwStreamFlags, LONGLONG llTimestamp, IMFSample *pSample);
+ STDMETHODIMP OnFlush(DWORD dwStreamIndex);
+ STDMETHODIMP OnEvent(DWORD dwStreamIndex, IMFMediaEvent *pEvent);
+
+Q_SIGNALS:
+ void sampleAdded();
+ void finished();
+
+private:
+ long m_cRef;
+ QList<IMFSample*> m_cachedSamples;
+ QMutex m_samplesMutex;
+
+ IMFSourceReader *m_sourceReader;
+ IMFMediaSource *m_source;
+};
+#endif//MFDECODERSOURCEREADER_H
diff --git a/src/multimedia/platform/wmf/evr/evr.pri b/src/multimedia/platform/wmf/evr/evr.pri
new file mode 100644
index 000000000..6168063e9
--- /dev/null
+++ b/src/multimedia/platform/wmf/evr/evr.pri
@@ -0,0 +1,20 @@
+INCLUDEPATH += $$PWD
+
+qtHaveModule(widgets): QT += widgets
+QT += gui-private
+
+LIBS += -lmf -lmfplat -lmfuuid -ld3d9 -ldxva2 -lwinmm -levr
+
+HEADERS += \
+ $$PWD/evrvideowindowcontrol_p.h \
+ $$PWD/evrcustompresenter_p.h \
+ $$PWD/evrd3dpresentengine_p.h \
+ $$PWD/evrhelpers_p.h \
+ $$PWD/evrdefs_p.h
+
+SOURCES += \
+ $$PWD/evrvideowindowcontrol.cpp \
+ $$PWD/evrcustompresenter.cpp \
+ $$PWD/evrd3dpresentengine.cpp \
+ $$PWD/evrhelpers.cpp \
+ $$PWD/evrdefs.cpp
diff --git a/src/multimedia/platform/wmf/evr/evrcustompresenter.cpp b/src/multimedia/platform/wmf/evr/evrcustompresenter.cpp
new file mode 100644
index 000000000..dd6c0021b
--- /dev/null
+++ b/src/multimedia/platform/wmf/evr/evrcustompresenter.cpp
@@ -0,0 +1,2062 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $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 "evrcustompresenter_p.h"
+
+#include "evrd3dpresentengine_p.h"
+#include "evrhelpers_p.h"
+
+#include <QtCore/qmutex.h>
+#include <QtCore/qvarlengtharray.h>
+#include <QtCore/qrect.h>
+#include <qabstractvideosurface.h>
+#include <qthread.h>
+#include <qcoreapplication.h>
+#include <qmath.h>
+#include <QtCore/qdebug.h>
+
+#include <mutex>
+
+#include <float.h>
+#include <evcode.h>
+
+QT_BEGIN_NAMESPACE
+
+const static MFRatio g_DefaultFrameRate = { 30, 1 };
+static const DWORD SCHEDULER_TIMEOUT = 5000;
+static const MFTIME ONE_SECOND = 10000000;
+static const LONG ONE_MSEC = 1000;
+
+// Function declarations.
+static HRESULT setDesiredSampleTime(IMFSample *sample, const LONGLONG& hnsSampleTime, const LONGLONG& hnsDuration);
+static HRESULT clearDesiredSampleTime(IMFSample *sample);
+static HRESULT setMixerSourceRect(IMFTransform *mixer, const MFVideoNormalizedRect& nrcSource);
+static QVideoFrame::PixelFormat pixelFormatFromMediaType(IMFMediaType *type);
+
+static inline LONG MFTimeToMsec(const LONGLONG& time)
+{
+ return (LONG)(time / (ONE_SECOND / ONE_MSEC));
+}
+
+bool qt_evr_setCustomPresenter(IUnknown *evr, EVRCustomPresenter *presenter)
+{
+ if (!evr || !presenter)
+ return false;
+
+ HRESULT result = E_FAIL;
+
+ IMFVideoRenderer *renderer = NULL;
+ if (SUCCEEDED(evr->QueryInterface(IID_PPV_ARGS(&renderer)))) {
+ result = renderer->InitializeRenderer(NULL, presenter);
+ renderer->Release();
+ }
+
+ return result == S_OK;
+}
+
+class PresentSampleEvent : public QEvent
+{
+public:
+ PresentSampleEvent(IMFSample *sample)
+ : QEvent(QEvent::Type(EVRCustomPresenter::PresentSample))
+ , m_sample(sample)
+ {
+ if (m_sample)
+ m_sample->AddRef();
+ }
+
+ ~PresentSampleEvent() override
+ {
+ if (m_sample)
+ m_sample->Release();
+ }
+
+ IMFSample *sample() const { return m_sample; }
+
+private:
+ IMFSample *m_sample;
+};
+
+Scheduler::Scheduler(EVRCustomPresenter *presenter)
+ : m_presenter(presenter)
+ , m_clock(NULL)
+ , m_threadID(0)
+ , m_schedulerThread(0)
+ , m_threadReadyEvent(0)
+ , m_flushEvent(0)
+ , m_playbackRate(1.0f)
+ , m_perFrameInterval(0)
+ , m_perFrame_1_4th(0)
+ , m_lastSampleTime(0)
+{
+}
+
+Scheduler::~Scheduler()
+{
+ qt_evr_safe_release(&m_clock);
+ for (int i = 0; i < m_scheduledSamples.size(); ++i)
+ m_scheduledSamples[i]->Release();
+ m_scheduledSamples.clear();
+}
+
+void Scheduler::setFrameRate(const MFRatio& fps)
+{
+ UINT64 AvgTimePerFrame = 0;
+
+ // Convert to a duration.
+ MFFrameRateToAverageTimePerFrame(fps.Numerator, fps.Denominator, &AvgTimePerFrame);
+
+ m_perFrameInterval = (MFTIME)AvgTimePerFrame;
+
+ // Calculate 1/4th of this value, because we use it frequently.
+ m_perFrame_1_4th = m_perFrameInterval / 4;
+}
+
+HRESULT Scheduler::startScheduler(IMFClock *clock)
+{
+ if (m_schedulerThread)
+ return E_UNEXPECTED;
+
+ HRESULT hr = S_OK;
+ DWORD dwID = 0;
+ HANDLE hObjects[2];
+ DWORD dwWait = 0;
+
+ if (m_clock)
+ m_clock->Release();
+ m_clock = clock;
+ if (m_clock)
+ m_clock->AddRef();
+
+ // Set a high the timer resolution (ie, short timer period).
+ timeBeginPeriod(1);
+
+ // Create an event to wait for the thread to start.
+ m_threadReadyEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+ if (!m_threadReadyEvent) {
+ hr = HRESULT_FROM_WIN32(GetLastError());
+ goto done;
+ }
+
+ // Create an event to wait for flush commands to complete.
+ m_flushEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+ if (!m_flushEvent) {
+ hr = HRESULT_FROM_WIN32(GetLastError());
+ goto done;
+ }
+
+ // Create the scheduler thread.
+ m_schedulerThread = CreateThread(NULL, 0, schedulerThreadProc, (LPVOID)this, 0, &dwID);
+ if (!m_schedulerThread) {
+ hr = HRESULT_FROM_WIN32(GetLastError());
+ goto done;
+ }
+
+ // Wait for the thread to signal the "thread ready" event.
+ hObjects[0] = m_threadReadyEvent;
+ hObjects[1] = m_schedulerThread;
+ dwWait = WaitForMultipleObjects(2, hObjects, FALSE, INFINITE); // Wait for EITHER of these handles.
+ if (WAIT_OBJECT_0 != dwWait) {
+ // The thread terminated early for some reason. This is an error condition.
+ CloseHandle(m_schedulerThread);
+ m_schedulerThread = NULL;
+
+ hr = E_UNEXPECTED;
+ goto done;
+ }
+
+ m_threadID = dwID;
+
+done:
+ // Regardless success/failure, we are done using the "thread ready" event.
+ if (m_threadReadyEvent) {
+ CloseHandle(m_threadReadyEvent);
+ m_threadReadyEvent = NULL;
+ }
+ return hr;
+}
+
+HRESULT Scheduler::stopScheduler()
+{
+ if (!m_schedulerThread)
+ return S_OK;
+
+ // Ask the scheduler thread to exit.
+ PostThreadMessage(m_threadID, Terminate, 0, 0);
+
+ // Wait for the thread to exit.
+ WaitForSingleObject(m_schedulerThread, INFINITE);
+
+ // Close handles.
+ CloseHandle(m_schedulerThread);
+ m_schedulerThread = NULL;
+
+ CloseHandle(m_flushEvent);
+ m_flushEvent = NULL;
+
+ // Discard samples.
+ m_mutex.lock();
+ for (int i = 0; i < m_scheduledSamples.size(); ++i)
+ m_scheduledSamples[i]->Release();
+ m_scheduledSamples.clear();
+ m_mutex.unlock();
+
+ // Restore the timer resolution.
+ timeEndPeriod(1);
+
+ return S_OK;
+}
+
+HRESULT Scheduler::flush()
+{
+ if (m_schedulerThread) {
+ // Ask the scheduler thread to flush.
+ PostThreadMessage(m_threadID, Flush, 0 , 0);
+
+ // Wait for the scheduler thread to signal the flush event,
+ // OR for the thread to terminate.
+ HANDLE objects[] = { m_flushEvent, m_schedulerThread };
+
+ WaitForMultipleObjects(ARRAYSIZE(objects), objects, FALSE, SCHEDULER_TIMEOUT);
+ }
+
+ return S_OK;
+}
+
+bool Scheduler::areSamplesScheduled()
+{
+ QMutexLocker locker(&m_mutex);
+ return m_scheduledSamples.count() > 0;
+}
+
+HRESULT Scheduler::scheduleSample(IMFSample *sample, bool presentNow)
+{
+ if (!m_schedulerThread)
+ return MF_E_NOT_INITIALIZED;
+
+ HRESULT hr = S_OK;
+ DWORD dwExitCode = 0;
+
+ GetExitCodeThread(m_schedulerThread, &dwExitCode);
+ if (dwExitCode != STILL_ACTIVE)
+ return E_FAIL;
+
+ if (presentNow || !m_clock) {
+ m_presenter->presentSample(sample);
+ } else {
+ // Queue the sample and ask the scheduler thread to wake up.
+ m_mutex.lock();
+ sample->AddRef();
+ m_scheduledSamples.enqueue(sample);
+ m_mutex.unlock();
+
+ if (SUCCEEDED(hr))
+ PostThreadMessage(m_threadID, Schedule, 0, 0);
+ }
+
+ return hr;
+}
+
+HRESULT Scheduler::processSamplesInQueue(LONG *nextSleep)
+{
+ HRESULT hr = S_OK;
+ LONG wait = 0;
+ IMFSample *sample = NULL;
+
+ // Process samples until the queue is empty or until the wait time > 0.
+ while (!m_scheduledSamples.isEmpty()) {
+ m_mutex.lock();
+ sample = m_scheduledSamples.dequeue();
+ m_mutex.unlock();
+
+ // Process the next sample in the queue. If the sample is not ready
+ // for presentation. the value returned in wait is > 0, which
+ // means the scheduler should sleep for that amount of time.
+
+ hr = processSample(sample, &wait);
+ qt_evr_safe_release(&sample);
+
+ if (FAILED(hr) || wait > 0)
+ break;
+ }
+
+ // If the wait time is zero, it means we stopped because the queue is
+ // empty (or an error occurred). Set the wait time to infinite; this will
+ // make the scheduler thread sleep until it gets another thread message.
+ if (wait == 0)
+ wait = INFINITE;
+
+ *nextSleep = wait;
+ return hr;
+}
+
+HRESULT Scheduler::processSample(IMFSample *sample, LONG *pNextSleep)
+{
+ HRESULT hr = S_OK;
+
+ LONGLONG hnsPresentationTime = 0;
+ LONGLONG hnsTimeNow = 0;
+ MFTIME hnsSystemTime = 0;
+
+ bool presentNow = true;
+ LONG nextSleep = 0;
+
+ if (m_clock) {
+ // Get the sample's time stamp. It is valid for a sample to
+ // have no time stamp.
+ hr = sample->GetSampleTime(&hnsPresentationTime);
+
+ // Get the clock time. (But if the sample does not have a time stamp,
+ // we don't need the clock time.)
+ if (SUCCEEDED(hr))
+ hr = m_clock->GetCorrelatedTime(0, &hnsTimeNow, &hnsSystemTime);
+
+ // Calculate the time until the sample's presentation time.
+ // A negative value means the sample is late.
+ LONGLONG hnsDelta = hnsPresentationTime - hnsTimeNow;
+ if (m_playbackRate < 0) {
+ // For reverse playback, the clock runs backward. Therefore, the
+ // delta is reversed.
+ hnsDelta = - hnsDelta;
+ }
+
+ if (hnsDelta < - m_perFrame_1_4th) {
+ // This sample is late.
+ presentNow = true;
+ } else if (hnsDelta > (3 * m_perFrame_1_4th)) {
+ // This sample is still too early. Go to sleep.
+ nextSleep = MFTimeToMsec(hnsDelta - (3 * m_perFrame_1_4th));
+
+ // Adjust the sleep time for the clock rate. (The presentation clock runs
+ // at m_fRate, but sleeping uses the system clock.)
+ if (m_playbackRate != 0)
+ nextSleep = (LONG)(nextSleep / qFabs(m_playbackRate));
+
+ // Don't present yet.
+ presentNow = false;
+ }
+ }
+
+ if (presentNow) {
+ m_presenter->presentSample(sample);
+ } else {
+ // The sample is not ready yet. Return it to the queue.
+ m_mutex.lock();
+ sample->AddRef();
+ m_scheduledSamples.prepend(sample);
+ m_mutex.unlock();
+ }
+
+ *pNextSleep = nextSleep;
+
+ return hr;
+}
+
+DWORD WINAPI Scheduler::schedulerThreadProc(LPVOID parameter)
+{
+ Scheduler* scheduler = reinterpret_cast<Scheduler*>(parameter);
+ if (!scheduler)
+ return -1;
+ return scheduler->schedulerThreadProcPrivate();
+}
+
+DWORD Scheduler::schedulerThreadProcPrivate()
+{
+ HRESULT hr = S_OK;
+ MSG msg;
+ LONG wait = INFINITE;
+ bool exitThread = false;
+
+ // Force the system to create a message queue for this thread.
+ // (See MSDN documentation for PostThreadMessage.)
+ PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
+
+ // Signal to the scheduler that the thread is ready.
+ SetEvent(m_threadReadyEvent);
+
+ while (!exitThread) {
+ // Wait for a thread message OR until the wait time expires.
+ DWORD result = MsgWaitForMultipleObjects(0, NULL, FALSE, wait, QS_POSTMESSAGE);
+
+ if (result == WAIT_TIMEOUT) {
+ // If we timed out, then process the samples in the queue
+ hr = processSamplesInQueue(&wait);
+ if (FAILED(hr))
+ exitThread = true;
+ }
+
+ while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
+ bool processSamples = true;
+
+ switch (msg.message) {
+ case Terminate:
+ exitThread = true;
+ break;
+ case Flush:
+ // Flushing: Clear the sample queue and set the event.
+ m_mutex.lock();
+ for (int i = 0; i < m_scheduledSamples.size(); ++i)
+ m_scheduledSamples[i]->Release();
+ m_scheduledSamples.clear();
+ m_mutex.unlock();
+ wait = INFINITE;
+ SetEvent(m_flushEvent);
+ break;
+ case Schedule:
+ // Process as many samples as we can.
+ if (processSamples) {
+ hr = processSamplesInQueue(&wait);
+ if (FAILED(hr))
+ exitThread = true;
+ processSamples = (wait != (LONG)INFINITE);
+ }
+ break;
+ }
+ }
+
+ }
+
+ return (SUCCEEDED(hr) ? 0 : 1);
+}
+
+
+SamplePool::SamplePool()
+ : m_initialized(false)
+{
+}
+
+SamplePool::~SamplePool()
+{
+ clear();
+}
+
+HRESULT SamplePool::getSample(IMFSample **sample)
+{
+ QMutexLocker locker(&m_mutex);
+
+ if (!m_initialized)
+ return MF_E_NOT_INITIALIZED;
+
+ if (m_videoSampleQueue.isEmpty())
+ return MF_E_SAMPLEALLOCATOR_EMPTY;
+
+ // Get a sample from the allocated queue.
+
+ // It doesn't matter if we pull them from the head or tail of the list,
+ // but when we get it back, we want to re-insert it onto the opposite end.
+ // (see ReturnSample)
+
+ IMFSample *taken = m_videoSampleQueue.takeFirst();
+
+ // Give the sample to the caller.
+ *sample = taken;
+ (*sample)->AddRef();
+
+ taken->Release();
+
+ return S_OK;
+}
+
+HRESULT SamplePool::returnSample(IMFSample *sample)
+{
+ QMutexLocker locker(&m_mutex);
+
+ if (!m_initialized)
+ return MF_E_NOT_INITIALIZED;
+
+ m_videoSampleQueue.append(sample);
+ sample->AddRef();
+
+ return S_OK;
+}
+
+HRESULT SamplePool::initialize(QList<IMFSample*> &samples)
+{
+ QMutexLocker locker(&m_mutex);
+
+ if (m_initialized)
+ return MF_E_INVALIDREQUEST;
+
+ // Move these samples into our allocated queue.
+ for (auto sample : qAsConst(samples)) {
+ sample->AddRef();
+ m_videoSampleQueue.append(sample);
+ }
+
+ m_initialized = true;
+
+ for (auto sample : qAsConst(samples))
+ sample->Release();
+ samples.clear();
+ return S_OK;
+}
+
+HRESULT SamplePool::clear()
+{
+ QMutexLocker locker(&m_mutex);
+
+ for (auto sample : qAsConst(m_videoSampleQueue))
+ sample->Release();
+ m_videoSampleQueue.clear();
+ m_initialized = false;
+
+ return S_OK;
+}
+
+
+EVRCustomPresenter::EVRCustomPresenter(QAbstractVideoSurface *surface)
+ : QObject()
+ , m_sampleFreeCB(this, &EVRCustomPresenter::onSampleFree)
+ , m_refCount(1)
+ , m_renderState(RenderShutdown)
+ , m_scheduler(this)
+ , m_tokenCounter(0)
+ , m_sampleNotify(false)
+ , m_repaint(false)
+ , m_prerolled(false)
+ , m_endStreaming(false)
+ , m_playbackRate(1.0f)
+ , m_presentEngine(new D3DPresentEngine)
+ , m_clock(0)
+ , m_mixer(0)
+ , m_mediaEventSink(0)
+ , m_mediaType(0)
+ , m_surface(0)
+ , m_canRenderToSurface(false)
+ , m_positionOffset(0)
+{
+ // Initial source rectangle = (0,0,1,1)
+ m_sourceRect.top = 0;
+ m_sourceRect.left = 0;
+ m_sourceRect.bottom = 1;
+ m_sourceRect.right = 1;
+
+ setSurface(surface);
+}
+
+EVRCustomPresenter::~EVRCustomPresenter()
+{
+ m_scheduler.flush();
+ m_scheduler.stopScheduler();
+ m_samplePool.clear();
+
+ qt_evr_safe_release(&m_clock);
+ qt_evr_safe_release(&m_mixer);
+ qt_evr_safe_release(&m_mediaEventSink);
+ qt_evr_safe_release(&m_mediaType);
+
+ delete m_presentEngine;
+}
+
+HRESULT EVRCustomPresenter::QueryInterface(REFIID riid, void ** ppvObject)
+{
+ if (!ppvObject)
+ return E_POINTER;
+ if (riid == IID_IMFGetService) {
+ *ppvObject = static_cast<IMFGetService*>(this);
+ } else if (riid == IID_IMFTopologyServiceLookupClient) {
+ *ppvObject = static_cast<IMFTopologyServiceLookupClient*>(this);
+ } else if (riid == IID_IMFVideoDeviceID) {
+ *ppvObject = static_cast<IMFVideoDeviceID*>(this);
+ } else if (riid == IID_IMFVideoPresenter) {
+ *ppvObject = static_cast<IMFVideoPresenter*>(this);
+ } else if (riid == IID_IMFRateSupport) {
+ *ppvObject = static_cast<IMFRateSupport*>(this);
+ } else if (riid == IID_IUnknown) {
+ *ppvObject = static_cast<IUnknown*>(static_cast<IMFGetService*>(this));
+ } else if (riid == IID_IMFClockStateSink) {
+ *ppvObject = static_cast<IMFClockStateSink*>(this);
+ } else {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+ AddRef();
+ return S_OK;
+}
+
+ULONG EVRCustomPresenter::AddRef()
+{
+ return InterlockedIncrement(&m_refCount);
+}
+
+ULONG EVRCustomPresenter::Release()
+{
+ ULONG uCount = InterlockedDecrement(&m_refCount);
+ if (uCount == 0)
+ delete this;
+ return uCount;
+}
+
+HRESULT EVRCustomPresenter::GetService(REFGUID guidService, REFIID riid, LPVOID *ppvObject)
+{
+ HRESULT hr = S_OK;
+
+ if (!ppvObject)
+ return E_POINTER;
+
+ // The only service GUID that we support is MR_VIDEO_RENDER_SERVICE.
+ if (guidService != mr_VIDEO_RENDER_SERVICE)
+ return MF_E_UNSUPPORTED_SERVICE;
+
+ // First try to get the service interface from the D3DPresentEngine object.
+ hr = m_presentEngine->getService(guidService, riid, ppvObject);
+ if (FAILED(hr))
+ // Next, check if this object supports the interface.
+ hr = QueryInterface(riid, ppvObject);
+
+ return hr;
+}
+
+HRESULT EVRCustomPresenter::GetDeviceID(IID* deviceID)
+{
+ if (!deviceID)
+ return E_POINTER;
+
+ *deviceID = iid_IDirect3DDevice9;
+
+ return S_OK;
+}
+
+HRESULT EVRCustomPresenter::InitServicePointers(IMFTopologyServiceLookup *lookup)
+{
+ if (!lookup)
+ return E_POINTER;
+
+ HRESULT hr = S_OK;
+ DWORD objectCount = 0;
+
+ const std::lock_guard<QRecursiveMutex> locker(m_mutex);
+
+ // Do not allow initializing when playing or paused.
+ if (isActive())
+ return MF_E_INVALIDREQUEST;
+
+ qt_evr_safe_release(&m_clock);
+ qt_evr_safe_release(&m_mixer);
+ qt_evr_safe_release(&m_mediaEventSink);
+
+ // Ask for the clock. Optional, because the EVR might not have a clock.
+ objectCount = 1;
+
+ lookup->LookupService(MF_SERVICE_LOOKUP_GLOBAL, 0,
+ mr_VIDEO_RENDER_SERVICE, IID_PPV_ARGS(&m_clock),
+ &objectCount
+ );
+
+ // Ask for the mixer. (Required.)
+ objectCount = 1;
+
+ hr = lookup->LookupService(MF_SERVICE_LOOKUP_GLOBAL, 0,
+ mr_VIDEO_MIXER_SERVICE, IID_PPV_ARGS(&m_mixer),
+ &objectCount
+ );
+
+ if (FAILED(hr))
+ return hr;
+
+ // Make sure that we can work with this mixer.
+ hr = configureMixer(m_mixer);
+ if (FAILED(hr))
+ return hr;
+
+ // Ask for the EVR's event-sink interface. (Required.)
+ objectCount = 1;
+
+ hr = lookup->LookupService(MF_SERVICE_LOOKUP_GLOBAL, 0,
+ mr_VIDEO_RENDER_SERVICE, IID_PPV_ARGS(&m_mediaEventSink),
+ &objectCount
+ );
+
+ if (SUCCEEDED(hr))
+ m_renderState = RenderStopped;
+
+ return hr;
+}
+
+HRESULT EVRCustomPresenter::ReleaseServicePointers()
+{
+ // Enter the shut-down state.
+ m_mutex.lock();
+
+ m_renderState = RenderShutdown;
+
+ m_mutex.unlock();
+
+ // Flush any samples that were scheduled.
+ flush();
+
+ // Clear the media type and release related resources.
+ setMediaType(NULL);
+
+ // Release all services that were acquired from InitServicePointers.
+ qt_evr_safe_release(&m_clock);
+ qt_evr_safe_release(&m_mixer);
+ qt_evr_safe_release(&m_mediaEventSink);
+
+ return S_OK;
+}
+
+bool EVRCustomPresenter::isValid() const
+{
+ return m_presentEngine->isValid() && m_canRenderToSurface;
+}
+
+HRESULT EVRCustomPresenter::ProcessMessage(MFVP_MESSAGE_TYPE message, ULONG_PTR param)
+{
+ HRESULT hr = S_OK;
+
+ const std::lock_guard<QRecursiveMutex> locker(m_mutex);
+
+ hr = checkShutdown();
+ if (FAILED(hr))
+ return hr;
+
+ switch (message) {
+ // Flush all pending samples.
+ case MFVP_MESSAGE_FLUSH:
+ hr = flush();
+ break;
+
+ // Renegotiate the media type with the mixer.
+ case MFVP_MESSAGE_INVALIDATEMEDIATYPE:
+ hr = renegotiateMediaType();
+ break;
+
+ // The mixer received a new input sample.
+ case MFVP_MESSAGE_PROCESSINPUTNOTIFY:
+ hr = processInputNotify();
+ break;
+
+ // Streaming is about to start.
+ case MFVP_MESSAGE_BEGINSTREAMING:
+ hr = beginStreaming();
+ break;
+
+ // Streaming has ended. (The EVR has stopped.)
+ case MFVP_MESSAGE_ENDSTREAMING:
+ hr = endStreaming();
+ break;
+
+ // All input streams have ended.
+ case MFVP_MESSAGE_ENDOFSTREAM:
+ // Set the EOS flag.
+ m_endStreaming = true;
+ // Check if it's time to send the EC_COMPLETE event to the EVR.
+ hr = checkEndOfStream();
+ break;
+
+ // Frame-stepping is starting.
+ case MFVP_MESSAGE_STEP:
+ hr = prepareFrameStep(DWORD(param));
+ break;
+
+ // Cancels frame-stepping.
+ case MFVP_MESSAGE_CANCELSTEP:
+ hr = cancelFrameStep();
+ break;
+
+ default:
+ hr = E_INVALIDARG; // Unknown message. This case should never occur.
+ break;
+ }
+
+ return hr;
+}
+
+HRESULT EVRCustomPresenter::GetCurrentMediaType(IMFVideoMediaType **mediaType)
+{
+ HRESULT hr = S_OK;
+
+ if (!mediaType)
+ return E_POINTER;
+
+ *mediaType = NULL;
+
+ const std::lock_guard<QRecursiveMutex> locker(m_mutex);
+
+ hr = checkShutdown();
+ if (FAILED(hr))
+ return hr;
+
+ if (!m_mediaType)
+ return MF_E_NOT_INITIALIZED;
+
+ return m_mediaType->QueryInterface(IID_PPV_ARGS(mediaType));
+}
+
+HRESULT EVRCustomPresenter::OnClockStart(MFTIME, LONGLONG clockStartOffset)
+{
+ const std::lock_guard<QRecursiveMutex> locker(m_mutex);
+
+ // We cannot start after shutdown.
+ HRESULT hr = checkShutdown();
+ if (FAILED(hr))
+ return hr;
+
+ // Check if the clock is already active (not stopped).
+ if (isActive()) {
+ m_renderState = RenderStarted;
+
+ // If the clock position changes while the clock is active, it
+ // is a seek request. We need to flush all pending samples.
+ if (clockStartOffset != PRESENTATION_CURRENT_POSITION)
+ flush();
+ } else {
+ m_renderState = RenderStarted;
+
+ // The clock has started from the stopped state.
+
+ // Possibly we are in the middle of frame-stepping OR have samples waiting
+ // in the frame-step queue. Deal with these two cases first:
+ hr = startFrameStep();
+ if (FAILED(hr))
+ return hr;
+ }
+
+ // Now try to get new output samples from the mixer.
+ processOutputLoop();
+
+ return hr;
+}
+
+HRESULT EVRCustomPresenter::OnClockRestart(MFTIME)
+{
+ const std::lock_guard<QRecursiveMutex> locker(m_mutex);
+
+ HRESULT hr = checkShutdown();
+ if (FAILED(hr))
+ return hr;
+
+ // The EVR calls OnClockRestart only while paused.
+
+ m_renderState = RenderStarted;
+
+ // Possibly we are in the middle of frame-stepping OR we have samples waiting
+ // in the frame-step queue. Deal with these two cases first:
+ hr = startFrameStep();
+ if (FAILED(hr))
+ return hr;
+
+ // Now resume the presentation loop.
+ processOutputLoop();
+
+ return hr;
+}
+
+HRESULT EVRCustomPresenter::OnClockStop(MFTIME)
+{
+ const std::lock_guard<QRecursiveMutex> locker(m_mutex);
+
+ HRESULT hr = checkShutdown();
+ if (FAILED(hr))
+ return hr;
+
+ if (m_renderState != RenderStopped) {
+ m_renderState = RenderStopped;
+ flush();
+
+ // If we are in the middle of frame-stepping, cancel it now.
+ if (m_frameStep.state != FrameStepNone)
+ cancelFrameStep();
+ }
+
+ return S_OK;
+}
+
+HRESULT EVRCustomPresenter::OnClockPause(MFTIME)
+{
+ const std::lock_guard<QRecursiveMutex> locker(m_mutex);
+
+ // We cannot pause the clock after shutdown.
+ HRESULT hr = checkShutdown();
+
+ if (SUCCEEDED(hr))
+ m_renderState = RenderPaused;
+
+ return hr;
+}
+
+HRESULT EVRCustomPresenter::OnClockSetRate(MFTIME, float rate)
+{
+ // Note:
+ // The presenter reports its maximum rate through the IMFRateSupport interface.
+ // Here, we assume that the EVR honors the maximum rate.
+
+ const std::lock_guard<QRecursiveMutex> locker(m_mutex);
+
+ HRESULT hr = checkShutdown();
+ if (FAILED(hr))
+ return hr;
+
+ // If the rate is changing from zero (scrubbing) to non-zero, cancel the
+ // frame-step operation.
+ if ((m_playbackRate == 0.0f) && (rate != 0.0f)) {
+ cancelFrameStep();
+ for (auto sample : qAsConst(m_frameStep.samples))
+ sample->Release();
+ m_frameStep.samples.clear();
+ }
+
+ m_playbackRate = rate;
+
+ // Tell the scheduler about the new rate.
+ m_scheduler.setClockRate(rate);
+
+ return S_OK;
+}
+
+HRESULT EVRCustomPresenter::GetSlowestRate(MFRATE_DIRECTION, BOOL, float *rate)
+{
+ if (!rate)
+ return E_POINTER;
+
+ const std::lock_guard<QRecursiveMutex> locker(m_mutex);
+
+ HRESULT hr = checkShutdown();
+
+ if (SUCCEEDED(hr)) {
+ // There is no minimum playback rate, so the minimum is zero.
+ *rate = 0;
+ }
+
+ return S_OK;
+}
+
+HRESULT EVRCustomPresenter::GetFastestRate(MFRATE_DIRECTION direction, BOOL thin, float *rate)
+{
+ if (!rate)
+ return E_POINTER;
+
+ const std::lock_guard<QRecursiveMutex> locker(m_mutex);
+
+ float maxRate = 0.0f;
+
+ HRESULT hr = checkShutdown();
+ if (FAILED(hr))
+ return hr;
+
+ // Get the maximum *forward* rate.
+ maxRate = getMaxRate(thin);
+
+ // For reverse playback, it's the negative of maxRate.
+ if (direction == MFRATE_REVERSE)
+ maxRate = -maxRate;
+
+ *rate = maxRate;
+
+ return S_OK;
+}
+
+HRESULT EVRCustomPresenter::IsRateSupported(BOOL thin, float rate, float *nearestSupportedRate)
+{
+ const std::lock_guard<QRecursiveMutex> locker(m_mutex);
+
+ float maxRate = 0.0f;
+ float nearestRate = rate; // If we support rate, that is the nearest.
+
+ HRESULT hr = checkShutdown();
+ if (FAILED(hr))
+ return hr;
+
+ // Find the maximum forward rate.
+ // Note: We have no minimum rate (that is, we support anything down to 0).
+ maxRate = getMaxRate(thin);
+
+ if (qFabs(rate) > maxRate) {
+ // The (absolute) requested rate exceeds the maximum rate.
+ hr = MF_E_UNSUPPORTED_RATE;
+
+ // The nearest supported rate is maxRate.
+ nearestRate = maxRate;
+ if (rate < 0) {
+ // Negative for reverse playback.
+ nearestRate = -nearestRate;
+ }
+ }
+
+ // Return the nearest supported rate.
+ if (nearestSupportedRate)
+ *nearestSupportedRate = nearestRate;
+
+ return hr;
+}
+
+void EVRCustomPresenter::supportedFormatsChanged()
+{
+ const std::lock_guard<QRecursiveMutex> locker(m_mutex);
+
+ m_canRenderToSurface = false;
+ m_presentEngine->setHint(D3DPresentEngine::RenderToTexture, false);
+
+ // check if we can render to the surface (compatible formats)
+ if (m_surface) {
+ QList<QVideoFrame::PixelFormat> formats = m_surface->supportedPixelFormats(QAbstractVideoBuffer::GLTextureHandle);
+ if (m_presentEngine->supportsTextureRendering() && formats.contains(QVideoFrame::Format_RGB32)) {
+ m_presentEngine->setHint(D3DPresentEngine::RenderToTexture, true);
+ m_canRenderToSurface = true;
+ } else {
+ formats = m_surface->supportedPixelFormats(QAbstractVideoBuffer::NoHandle);
+ for (QVideoFrame::PixelFormat format : qAsConst(formats)) {
+ if (SUCCEEDED(m_presentEngine->checkFormat(qt_evr_D3DFormatFromPixelFormat(format)))) {
+ m_canRenderToSurface = true;
+ break;
+ }
+ }
+ }
+ }
+
+ // TODO: if media type already set, renegotiate?
+}
+
+void EVRCustomPresenter::setSurface(QAbstractVideoSurface *surface)
+{
+ m_mutex.lock();
+
+ if (m_surface) {
+ disconnect(m_surface, &QAbstractVideoSurface::supportedFormatsChanged,
+ this, &EVRCustomPresenter::supportedFormatsChanged);
+ }
+
+ m_surface = surface;
+
+ if (m_surface) {
+ connect(m_surface, &QAbstractVideoSurface::supportedFormatsChanged,
+ this, &EVRCustomPresenter::supportedFormatsChanged);
+ }
+
+ m_mutex.unlock();
+
+ supportedFormatsChanged();
+}
+
+HRESULT EVRCustomPresenter::configureMixer(IMFTransform *mixer)
+{
+ // Set the zoom rectangle (ie, the source clipping rectangle).
+ return setMixerSourceRect(mixer, m_sourceRect);
+}
+
+HRESULT EVRCustomPresenter::renegotiateMediaType()
+{
+ HRESULT hr = S_OK;
+ bool foundMediaType = false;
+
+ IMFMediaType *mixerType = NULL;
+ IMFMediaType *optimalType = NULL;
+
+ if (!m_mixer)
+ return MF_E_INVALIDREQUEST;
+
+ // Loop through all of the mixer's proposed output types.
+ DWORD typeIndex = 0;
+ while (!foundMediaType && (hr != MF_E_NO_MORE_TYPES)) {
+ qt_evr_safe_release(&mixerType);
+ qt_evr_safe_release(&optimalType);
+
+ // Step 1. Get the next media type supported by mixer.
+ hr = m_mixer->GetOutputAvailableType(0, typeIndex++, &mixerType);
+ if (FAILED(hr))
+ break;
+
+ // From now on, if anything in this loop fails, try the next type,
+ // until we succeed or the mixer runs out of types.
+
+ // Step 2. Check if we support this media type.
+ if (SUCCEEDED(hr))
+ hr = isMediaTypeSupported(mixerType);
+
+ // Step 3. Adjust the mixer's type to match our requirements.
+ if (SUCCEEDED(hr))
+ hr = createOptimalVideoType(mixerType, &optimalType);
+
+ // Step 4. Check if the mixer will accept this media type.
+ if (SUCCEEDED(hr))
+ hr = m_mixer->SetOutputType(0, optimalType, MFT_SET_TYPE_TEST_ONLY);
+
+ // Step 5. Try to set the media type on ourselves.
+ if (SUCCEEDED(hr))
+ hr = setMediaType(optimalType);
+
+ // Step 6. Set output media type on mixer.
+ if (SUCCEEDED(hr)) {
+ hr = m_mixer->SetOutputType(0, optimalType, 0);
+
+ // If something went wrong, clear the media type.
+ if (FAILED(hr))
+ setMediaType(NULL);
+ }
+
+ if (SUCCEEDED(hr))
+ foundMediaType = true;
+ }
+
+ qt_evr_safe_release(&mixerType);
+ qt_evr_safe_release(&optimalType);
+
+ return hr;
+}
+
+HRESULT EVRCustomPresenter::flush()
+{
+ m_prerolled = false;
+
+ // The scheduler might have samples that are waiting for
+ // their presentation time. Tell the scheduler to flush.
+
+ // This call blocks until the scheduler threads discards all scheduled samples.
+ m_scheduler.flush();
+
+ // Flush the frame-step queue.
+ for (auto sample : qAsConst(m_frameStep.samples))
+ sample->Release();
+ m_frameStep.samples.clear();
+
+ if (m_renderState == RenderStopped && m_surface && m_surface->isActive()) {
+ // Repaint with black.
+ presentSample(NULL);
+ }
+
+ return S_OK;
+}
+
+HRESULT EVRCustomPresenter::processInputNotify()
+{
+ HRESULT hr = S_OK;
+
+ // Set the flag that says the mixer has a new sample.
+ m_sampleNotify = true;
+
+ if (!m_mediaType) {
+ // We don't have a valid media type yet.
+ hr = MF_E_TRANSFORM_TYPE_NOT_SET;
+ } else {
+ // Try to process an output sample.
+ processOutputLoop();
+ }
+ return hr;
+}
+
+HRESULT EVRCustomPresenter::beginStreaming()
+{
+ HRESULT hr = S_OK;
+
+ // Start the scheduler thread.
+ hr = m_scheduler.startScheduler(m_clock);
+
+ return hr;
+}
+
+HRESULT EVRCustomPresenter::endStreaming()
+{
+ HRESULT hr = S_OK;
+
+ // Stop the scheduler thread.
+ hr = m_scheduler.stopScheduler();
+
+ return hr;
+}
+
+HRESULT EVRCustomPresenter::checkEndOfStream()
+{
+ if (!m_endStreaming) {
+ // The EVR did not send the MFVP_MESSAGE_ENDOFSTREAM message.
+ return S_OK;
+ }
+
+ if (m_sampleNotify) {
+ // The mixer still has input.
+ return S_OK;
+ }
+
+ if (m_scheduler.areSamplesScheduled()) {
+ // Samples are still scheduled for rendering.
+ return S_OK;
+ }
+
+ // Everything is complete. Now we can tell the EVR that we are done.
+ notifyEvent(EC_COMPLETE, (LONG_PTR)S_OK, 0);
+ m_endStreaming = false;
+
+ stopSurface();
+ return S_OK;
+}
+
+HRESULT EVRCustomPresenter::prepareFrameStep(DWORD steps)
+{
+ HRESULT hr = S_OK;
+
+ // Cache the step count.
+ m_frameStep.steps += steps;
+
+ // Set the frame-step state.
+ m_frameStep.state = FrameStepWaitingStart;
+
+ // If the clock is are already running, we can start frame-stepping now.
+ // Otherwise, we will start when the clock starts.
+ if (m_renderState == RenderStarted)
+ hr = startFrameStep();
+
+ return hr;
+}
+
+HRESULT EVRCustomPresenter::startFrameStep()
+{
+ HRESULT hr = S_OK;
+ IMFSample *sample = NULL;
+
+ if (m_frameStep.state == FrameStepWaitingStart) {
+ // We have a frame-step request, and are waiting for the clock to start.
+ // Set the state to "pending," which means we are waiting for samples.
+ m_frameStep.state = FrameStepPending;
+
+ // If the frame-step queue already has samples, process them now.
+ while (!m_frameStep.samples.isEmpty() && (m_frameStep.state == FrameStepPending)) {
+ sample = m_frameStep.samples.takeFirst();
+
+ hr = deliverFrameStepSample(sample);
+ if (FAILED(hr))
+ goto done;
+
+ qt_evr_safe_release(&sample);
+
+ // We break from this loop when:
+ // (a) the frame-step queue is empty, or
+ // (b) the frame-step operation is complete.
+ }
+ } else if (m_frameStep.state == FrameStepNone) {
+ // We are not frame stepping. Therefore, if the frame-step queue has samples,
+ // we need to process them normally.
+ while (!m_frameStep.samples.isEmpty()) {
+ sample = m_frameStep.samples.takeFirst();
+
+ hr = deliverSample(sample, false);
+ if (FAILED(hr))
+ goto done;
+
+ qt_evr_safe_release(&sample);
+ }
+ }
+
+done:
+ qt_evr_safe_release(&sample);
+ return hr;
+}
+
+HRESULT EVRCustomPresenter::completeFrameStep(IMFSample *sample)
+{
+ HRESULT hr = S_OK;
+ MFTIME sampleTime = 0;
+ MFTIME systemTime = 0;
+
+ // Update our state.
+ m_frameStep.state = FrameStepComplete;
+ m_frameStep.sampleNoRef = 0;
+
+ // Notify the EVR that the frame-step is complete.
+ notifyEvent(EC_STEP_COMPLETE, FALSE, 0); // FALSE = completed (not cancelled)
+
+ // If we are scrubbing (rate == 0), also send the "scrub time" event.
+ if (isScrubbing()) {
+ // Get the time stamp from the sample.
+ hr = sample->GetSampleTime(&sampleTime);
+ if (FAILED(hr)) {
+ // No time stamp. Use the current presentation time.
+ if (m_clock)
+ m_clock->GetCorrelatedTime(0, &sampleTime, &systemTime);
+
+ hr = S_OK; // (Not an error condition.)
+ }
+
+ notifyEvent(EC_SCRUB_TIME, DWORD(sampleTime), DWORD(((sampleTime) >> 32) & 0xffffffff));
+ }
+ return hr;
+}
+
+HRESULT EVRCustomPresenter::cancelFrameStep()
+{
+ FrameStepState oldState = m_frameStep.state;
+
+ m_frameStep.state = FrameStepNone;
+ m_frameStep.steps = 0;
+ m_frameStep.sampleNoRef = 0;
+ // Don't clear the frame-step queue yet, because we might frame step again.
+
+ if (oldState > FrameStepNone && oldState < FrameStepComplete) {
+ // We were in the middle of frame-stepping when it was cancelled.
+ // Notify the EVR.
+ notifyEvent(EC_STEP_COMPLETE, TRUE, 0); // TRUE = cancelled
+ }
+ return S_OK;
+}
+
+HRESULT EVRCustomPresenter::createOptimalVideoType(IMFMediaType *proposedType, IMFMediaType **optimalType)
+{
+ HRESULT hr = S_OK;
+
+ RECT rcOutput;
+ ZeroMemory(&rcOutput, sizeof(rcOutput));
+
+ MFVideoArea displayArea;
+ ZeroMemory(&displayArea, sizeof(displayArea));
+
+ IMFMediaType *mtOptimal = NULL;
+
+ UINT64 size;
+ int width;
+ int height;
+
+ // Clone the proposed type.
+
+ hr = MFCreateMediaType(&mtOptimal);
+ if (FAILED(hr))
+ goto done;
+
+ hr = proposedType->CopyAllItems(mtOptimal);
+ if (FAILED(hr))
+ goto done;
+
+ // Modify the new type.
+
+ hr = proposedType->GetUINT64(MF_MT_FRAME_SIZE, &size);
+ width = int(HI32(size));
+ height = int(LO32(size));
+ rcOutput.left = 0;
+ rcOutput.top = 0;
+ rcOutput.right = width;
+ rcOutput.bottom = height;
+
+ // Set the geometric aperture, and disable pan/scan.
+ displayArea = qt_evr_makeMFArea(0, 0, rcOutput.right, rcOutput.bottom);
+
+ hr = mtOptimal->SetUINT32(MF_MT_PAN_SCAN_ENABLED, FALSE);
+ if (FAILED(hr))
+ goto done;
+
+ hr = mtOptimal->SetBlob(MF_MT_GEOMETRIC_APERTURE, reinterpret_cast<UINT8*>(&displayArea),
+ sizeof(displayArea));
+ if (FAILED(hr))
+ goto done;
+
+ // Set the pan/scan aperture and the minimum display aperture. We don't care
+ // about them per se, but the mixer will reject the type if these exceed the
+ // frame dimentions.
+ hr = mtOptimal->SetBlob(MF_MT_PAN_SCAN_APERTURE, reinterpret_cast<UINT8*>(&displayArea),
+ sizeof(displayArea));
+ if (FAILED(hr))
+ goto done;
+
+ hr = mtOptimal->SetBlob(MF_MT_MINIMUM_DISPLAY_APERTURE, reinterpret_cast<UINT8*>(&displayArea),
+ sizeof(displayArea));
+ if (FAILED(hr))
+ goto done;
+
+ // Return the pointer to the caller.
+ *optimalType = mtOptimal;
+ (*optimalType)->AddRef();
+
+done:
+ qt_evr_safe_release(&mtOptimal);
+ return hr;
+
+}
+
+HRESULT EVRCustomPresenter::setMediaType(IMFMediaType *mediaType)
+{
+ // Note: mediaType can be NULL (to clear the type)
+
+ // Clearing the media type is allowed in any state (including shutdown).
+ if (!mediaType) {
+ stopSurface();
+ qt_evr_safe_release(&m_mediaType);
+ releaseResources();
+ return S_OK;
+ }
+
+ MFRatio fps = { 0, 0 };
+ QList<IMFSample*> sampleQueue;
+
+ // Cannot set the media type after shutdown.
+ HRESULT hr = checkShutdown();
+ if (FAILED(hr))
+ goto done;
+
+ // Check if the new type is actually different.
+ // Note: This function safely handles NULL input parameters.
+ if (qt_evr_areMediaTypesEqual(m_mediaType, mediaType))
+ goto done; // Nothing more to do.
+
+ // We're really changing the type. First get rid of the old type.
+ qt_evr_safe_release(&m_mediaType);
+ releaseResources();
+
+ // Initialize the presenter engine with the new media type.
+ // The presenter engine allocates the samples.
+
+ hr = m_presentEngine->createVideoSamples(mediaType, sampleQueue);
+ if (FAILED(hr))
+ goto done;
+
+ // Mark each sample with our token counter. If this batch of samples becomes
+ // invalid, we increment the counter, so that we know they should be discarded.
+ for (auto sample : qAsConst(sampleQueue)) {
+ hr = sample->SetUINT32(MFSamplePresenter_SampleCounter, m_tokenCounter);
+ if (FAILED(hr))
+ goto done;
+ }
+
+ // Add the samples to the sample pool.
+ hr = m_samplePool.initialize(sampleQueue);
+ if (FAILED(hr))
+ goto done;
+
+ // Set the frame rate on the scheduler.
+ if (SUCCEEDED(qt_evr_getFrameRate(mediaType, &fps)) && (fps.Numerator != 0) && (fps.Denominator != 0)) {
+ m_scheduler.setFrameRate(fps);
+ } else {
+ // NOTE: The mixer's proposed type might not have a frame rate, in which case
+ // we'll use an arbitrary default. (Although it's unlikely the video source
+ // does not have a frame rate.)
+ m_scheduler.setFrameRate(g_DefaultFrameRate);
+ }
+
+ // Store the media type.
+ m_mediaType = mediaType;
+ m_mediaType->AddRef();
+
+ startSurface();
+
+done:
+ if (FAILED(hr))
+ releaseResources();
+ return hr;
+}
+
+HRESULT EVRCustomPresenter::isMediaTypeSupported(IMFMediaType *proposed)
+{
+ D3DFORMAT d3dFormat = D3DFMT_UNKNOWN;
+ BOOL compressed = FALSE;
+ MFVideoInterlaceMode interlaceMode = MFVideoInterlace_Unknown;
+ MFVideoArea videoCropArea;
+ UINT32 width = 0, height = 0;
+
+ // Validate the format.
+ HRESULT hr = qt_evr_getFourCC(proposed, reinterpret_cast<DWORD*>(&d3dFormat));
+ if (FAILED(hr))
+ return hr;
+
+ QVideoFrame::PixelFormat pixelFormat = pixelFormatFromMediaType(proposed);
+ if (pixelFormat == QVideoFrame::Format_Invalid)
+ return MF_E_INVALIDMEDIATYPE;
+
+ // When not rendering to texture, only accept pixel formats supported by the video surface
+ if (!m_presentEngine->isTextureRenderingEnabled()
+ && m_surface
+ && !m_surface->supportedPixelFormats().contains(pixelFormat)) {
+ return MF_E_INVALIDMEDIATYPE;
+ }
+
+ // Reject compressed media types.
+ hr = proposed->IsCompressedFormat(&compressed);
+ if (FAILED(hr))
+ return hr;
+
+ if (compressed)
+ return MF_E_INVALIDMEDIATYPE;
+
+ // The D3DPresentEngine checks whether surfaces can be created using this format
+ hr = m_presentEngine->checkFormat(d3dFormat);
+ if (FAILED(hr))
+ return hr;
+
+ // Reject interlaced formats.
+ hr = proposed->GetUINT32(MF_MT_INTERLACE_MODE, reinterpret_cast<UINT32*>(&interlaceMode));
+ if (FAILED(hr))
+ return hr;
+
+ if (interlaceMode != MFVideoInterlace_Progressive)
+ return MF_E_INVALIDMEDIATYPE;
+
+ hr = MFGetAttributeSize(proposed, MF_MT_FRAME_SIZE, &width, &height);
+ if (FAILED(hr))
+ return hr;
+
+ // Validate the various apertures (cropping regions) against the frame size.
+ // Any of these apertures may be unspecified in the media type, in which case
+ // we ignore it. We just want to reject invalid apertures.
+
+ if (SUCCEEDED(proposed->GetBlob(MF_MT_PAN_SCAN_APERTURE,
+ reinterpret_cast<UINT8*>(&videoCropArea),
+ sizeof(videoCropArea), nullptr))) {
+ hr = qt_evr_validateVideoArea(videoCropArea, width, height);
+ }
+ if (SUCCEEDED(proposed->GetBlob(MF_MT_GEOMETRIC_APERTURE,
+ reinterpret_cast<UINT8*>(&videoCropArea),
+ sizeof(videoCropArea), nullptr))) {
+ hr = qt_evr_validateVideoArea(videoCropArea, width, height);
+ }
+ if (SUCCEEDED(proposed->GetBlob(MF_MT_MINIMUM_DISPLAY_APERTURE,
+ reinterpret_cast<UINT8*>(&videoCropArea),
+ sizeof(videoCropArea), nullptr))) {
+ hr = qt_evr_validateVideoArea(videoCropArea, width, height);
+ }
+ return hr;
+}
+
+void EVRCustomPresenter::processOutputLoop()
+{
+ HRESULT hr = S_OK;
+
+ // Process as many samples as possible.
+ while (hr == S_OK) {
+ // If the mixer doesn't have a new input sample, break from the loop.
+ if (!m_sampleNotify) {
+ hr = MF_E_TRANSFORM_NEED_MORE_INPUT;
+ break;
+ }
+
+ // Try to process a sample.
+ hr = processOutput();
+
+ // NOTE: ProcessOutput can return S_FALSE to indicate it did not
+ // process a sample. If so, break out of the loop.
+ }
+
+ if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
+ // The mixer has run out of input data. Check for end-of-stream.
+ checkEndOfStream();
+ }
+}
+
+HRESULT EVRCustomPresenter::processOutput()
+{
+ HRESULT hr = S_OK;
+ DWORD status = 0;
+ LONGLONG mixerStartTime = 0, mixerEndTime = 0;
+ MFTIME systemTime = 0;
+ BOOL repaint = m_repaint; // Temporarily store this state flag.
+
+ MFT_OUTPUT_DATA_BUFFER dataBuffer;
+ ZeroMemory(&dataBuffer, sizeof(dataBuffer));
+
+ IMFSample *sample = NULL;
+
+ // If the clock is not running, we present the first sample,
+ // and then don't present any more until the clock starts.
+
+ if ((m_renderState != RenderStarted) && !m_repaint && m_prerolled)
+ return S_FALSE;
+
+ // Make sure we have a pointer to the mixer.
+ if (!m_mixer)
+ return MF_E_INVALIDREQUEST;
+
+ // Try to get a free sample from the video sample pool.
+ hr = m_samplePool.getSample(&sample);
+ if (hr == MF_E_SAMPLEALLOCATOR_EMPTY) // No free samples. Try again when a sample is released.
+ return S_FALSE;
+ if (FAILED(hr))
+ return hr;
+
+ // From now on, we have a valid video sample pointer, where the mixer will
+ // write the video data.
+
+ if (m_repaint) {
+ // Repaint request. Ask the mixer for the most recent sample.
+ setDesiredSampleTime(sample, m_scheduler.lastSampleTime(), m_scheduler.frameDuration());
+
+ m_repaint = false; // OK to clear this flag now.
+ } else {
+ // Not a repaint request. Clear the desired sample time; the mixer will
+ // give us the next frame in the stream.
+ clearDesiredSampleTime(sample);
+
+ if (m_clock) {
+ // Latency: Record the starting time for ProcessOutput.
+ m_clock->GetCorrelatedTime(0, &mixerStartTime, &systemTime);
+ }
+ }
+
+ // Now we are ready to get an output sample from the mixer.
+ dataBuffer.dwStreamID = 0;
+ dataBuffer.pSample = sample;
+ dataBuffer.dwStatus = 0;
+
+ hr = m_mixer->ProcessOutput(0, 1, &dataBuffer, &status);
+
+ if (FAILED(hr)) {
+ // Return the sample to the pool.
+ HRESULT hr2 = m_samplePool.returnSample(sample);
+ if (FAILED(hr2)) {
+ hr = hr2;
+ goto done;
+ }
+ // Handle some known error codes from ProcessOutput.
+ if (hr == MF_E_TRANSFORM_TYPE_NOT_SET) {
+ // The mixer's format is not set. Negotiate a new format.
+ hr = renegotiateMediaType();
+ } else if (hr == MF_E_TRANSFORM_STREAM_CHANGE) {
+ // There was a dynamic media type change. Clear our media type.
+ setMediaType(NULL);
+ } else if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
+ // The mixer needs more input.
+ // We have to wait for the mixer to get more input.
+ m_sampleNotify = false;
+ }
+ } else {
+ // We got an output sample from the mixer.
+
+ if (m_clock && !repaint) {
+ // Latency: Record the ending time for the ProcessOutput operation,
+ // and notify the EVR of the latency.
+
+ m_clock->GetCorrelatedTime(0, &mixerEndTime, &systemTime);
+
+ LONGLONG latencyTime = mixerEndTime - mixerStartTime;
+ notifyEvent(EC_PROCESSING_LATENCY, reinterpret_cast<LONG_PTR>(&latencyTime), 0);
+ }
+
+ // Set up notification for when the sample is released.
+ hr = trackSample(sample);
+ if (FAILED(hr))
+ goto done;
+
+ // Schedule the sample.
+ if ((m_frameStep.state == FrameStepNone) || repaint) {
+ hr = deliverSample(sample, repaint);
+ if (FAILED(hr))
+ goto done;
+ } else {
+ // We are frame-stepping (and this is not a repaint request).
+ hr = deliverFrameStepSample(sample);
+ if (FAILED(hr))
+ goto done;
+ }
+
+ m_prerolled = true; // We have presented at least one sample now.
+ }
+
+done:
+ qt_evr_safe_release(&sample);
+
+ // Important: Release any events returned from the ProcessOutput method.
+ qt_evr_safe_release(&dataBuffer.pEvents);
+ return hr;
+}
+
+HRESULT EVRCustomPresenter::deliverSample(IMFSample *sample, bool repaint)
+{
+ // If we are not actively playing, OR we are scrubbing (rate = 0) OR this is a
+ // repaint request, then we need to present the sample immediately. Otherwise,
+ // schedule it normally.
+
+ bool presentNow = ((m_renderState != RenderStarted) || isScrubbing() || repaint);
+
+ HRESULT hr = m_scheduler.scheduleSample(sample, presentNow);
+
+ if (FAILED(hr)) {
+ // Notify the EVR that we have failed during streaming. The EVR will notify the
+ // pipeline.
+
+ notifyEvent(EC_ERRORABORT, hr, 0);
+ }
+
+ return hr;
+}
+
+HRESULT EVRCustomPresenter::deliverFrameStepSample(IMFSample *sample)
+{
+ HRESULT hr = S_OK;
+ IUnknown *unk = NULL;
+
+ // For rate 0, discard any sample that ends earlier than the clock time.
+ if (isScrubbing() && m_clock && qt_evr_isSampleTimePassed(m_clock, sample)) {
+ // Discard this sample.
+ } else if (m_frameStep.state >= FrameStepScheduled) {
+ // A frame was already submitted. Put this sample on the frame-step queue,
+ // in case we are asked to step to the next frame. If frame-stepping is
+ // cancelled, this sample will be processed normally.
+ sample->AddRef();
+ m_frameStep.samples.append(sample);
+ } else {
+ // We're ready to frame-step.
+
+ // Decrement the number of steps.
+ if (m_frameStep.steps > 0)
+ m_frameStep.steps--;
+
+ if (m_frameStep.steps > 0) {
+ // This is not the last step. Discard this sample.
+ } else if (m_frameStep.state == FrameStepWaitingStart) {
+ // This is the right frame, but the clock hasn't started yet. Put the
+ // sample on the frame-step queue. When the clock starts, the sample
+ // will be processed.
+ sample->AddRef();
+ m_frameStep.samples.append(sample);
+ } else {
+ // This is the right frame *and* the clock has started. Deliver this sample.
+ hr = deliverSample(sample, false);
+ if (FAILED(hr))
+ goto done;
+
+ // Query for IUnknown so that we can identify the sample later.
+ // Per COM rules, an object always returns the same pointer when QI'ed for IUnknown.
+ hr = sample->QueryInterface(IID_PPV_ARGS(&unk));
+ if (FAILED(hr))
+ goto done;
+
+ m_frameStep.sampleNoRef = reinterpret_cast<DWORD_PTR>(unk); // No add-ref.
+
+ // NOTE: We do not AddRef the IUnknown pointer, because that would prevent the
+ // sample from invoking the OnSampleFree callback after the sample is presented.
+ // We use this IUnknown pointer purely to identify the sample later; we never
+ // attempt to dereference the pointer.
+
+ m_frameStep.state = FrameStepScheduled;
+ }
+ }
+done:
+ qt_evr_safe_release(&unk);
+ return hr;
+}
+
+HRESULT EVRCustomPresenter::trackSample(IMFSample *sample)
+{
+ IMFTrackedSample *tracked = NULL;
+
+ HRESULT hr = sample->QueryInterface(IID_PPV_ARGS(&tracked));
+
+ if (SUCCEEDED(hr))
+ hr = tracked->SetAllocator(&m_sampleFreeCB, NULL);
+
+ qt_evr_safe_release(&tracked);
+ return hr;
+}
+
+void EVRCustomPresenter::releaseResources()
+{
+ // Increment the token counter to indicate that all existing video samples
+ // are "stale." As these samples get released, we'll dispose of them.
+ //
+ // Note: The token counter is required because the samples are shared
+ // between more than one thread, and they are returned to the presenter
+ // through an asynchronous callback (onSampleFree). Without the token, we
+ // might accidentally re-use a stale sample after the ReleaseResources
+ // method returns.
+
+ m_tokenCounter++;
+
+ flush();
+
+ m_samplePool.clear();
+
+ m_presentEngine->releaseResources();
+}
+
+HRESULT EVRCustomPresenter::onSampleFree(IMFAsyncResult *result)
+{
+ IUnknown *object = NULL;
+ IMFSample *sample = NULL;
+ IUnknown *unk = NULL;
+ UINT32 token;
+
+ // Get the sample from the async result object.
+ HRESULT hr = result->GetObject(&object);
+ if (FAILED(hr))
+ goto done;
+
+ hr = object->QueryInterface(IID_PPV_ARGS(&sample));
+ if (FAILED(hr))
+ goto done;
+
+ // If this sample was submitted for a frame-step, the frame step operation
+ // is complete.
+
+ if (m_frameStep.state == FrameStepScheduled) {
+ // Query the sample for IUnknown and compare it to our cached value.
+ hr = sample->QueryInterface(IID_PPV_ARGS(&unk));
+ if (FAILED(hr))
+ goto done;
+
+ if (m_frameStep.sampleNoRef == reinterpret_cast<DWORD_PTR>(unk)) {
+ // Notify the EVR.
+ hr = completeFrameStep(sample);
+ if (FAILED(hr))
+ goto done;
+ }
+
+ // Note: Although object is also an IUnknown pointer, it is not
+ // guaranteed to be the exact pointer value returned through
+ // QueryInterface. Therefore, the second QueryInterface call is
+ // required.
+ }
+
+ m_mutex.lock();
+
+ token = MFGetAttributeUINT32(sample, MFSamplePresenter_SampleCounter, (UINT32)-1);
+
+ if (token == m_tokenCounter) {
+ // Return the sample to the sample pool.
+ hr = m_samplePool.returnSample(sample);
+ if (SUCCEEDED(hr)) {
+ // A free sample is available. Process more data if possible.
+ processOutputLoop();
+ }
+ }
+
+ m_mutex.unlock();
+
+done:
+ if (FAILED(hr))
+ notifyEvent(EC_ERRORABORT, hr, 0);
+ qt_evr_safe_release(&object);
+ qt_evr_safe_release(&sample);
+ qt_evr_safe_release(&unk);
+ return hr;
+}
+
+float EVRCustomPresenter::getMaxRate(bool thin)
+{
+ // Non-thinned:
+ // If we have a valid frame rate and a monitor refresh rate, the maximum
+ // playback rate is equal to the refresh rate. Otherwise, the maximum rate
+ // is unbounded (FLT_MAX).
+
+ // Thinned: The maximum rate is unbounded.
+
+ float maxRate = FLT_MAX;
+ MFRatio fps = { 0, 0 };
+ UINT monitorRateHz = 0;
+
+ if (!thin && m_mediaType) {
+ qt_evr_getFrameRate(m_mediaType, &fps);
+ monitorRateHz = m_presentEngine->refreshRate();
+
+ if (fps.Denominator && fps.Numerator && monitorRateHz) {
+ // Max Rate = Refresh Rate / Frame Rate
+ maxRate = (float)MulDiv(monitorRateHz, fps.Denominator, fps.Numerator);
+ }
+ }
+
+ return maxRate;
+}
+
+bool EVRCustomPresenter::event(QEvent *e)
+{
+ switch (int(e->type())) {
+ case StartSurface:
+ startSurface();
+ return true;
+ case StopSurface:
+ stopSurface();
+ return true;
+ case PresentSample:
+ presentSample(static_cast<PresentSampleEvent *>(e)->sample());
+ return true;
+ default:
+ break;
+ }
+ return QObject::event(e);
+}
+
+void EVRCustomPresenter::startSurface()
+{
+ if (thread() != QThread::currentThread()) {
+ QCoreApplication::postEvent(this, new QEvent(QEvent::Type(StartSurface)));
+ return;
+ }
+
+ if (!m_surface || m_surface->isActive())
+ return;
+
+ QVideoSurfaceFormat format = m_presentEngine->videoSurfaceFormat();
+ if (!format.isValid())
+ return;
+
+ m_surface->start(format);
+}
+
+void EVRCustomPresenter::stopSurface()
+{
+ if (thread() != QThread::currentThread()) {
+ QCoreApplication::postEvent(this, new QEvent(QEvent::Type(StopSurface)));
+ return;
+ }
+
+ if (!m_surface || !m_surface->isActive())
+ return;
+
+ m_surface->stop();
+}
+
+void EVRCustomPresenter::presentSample(IMFSample *sample)
+{
+ if (thread() != QThread::currentThread()) {
+ QCoreApplication::postEvent(this, new PresentSampleEvent(sample));
+ return;
+ }
+
+ if (!m_surface || !m_presentEngine->videoSurfaceFormat().isValid())
+ return;
+
+ QVideoFrame frame = m_presentEngine->makeVideoFrame(sample);
+
+ // Since start/end times are related to a position when the clock is started,
+ // to have times from the beginning, need to adjust it by adding seeked position.
+ if (m_positionOffset) {
+ if (frame.startTime())
+ frame.setStartTime(frame.startTime() + m_positionOffset);
+ if (frame.endTime())
+ frame.setEndTime(frame.endTime() + m_positionOffset);
+ }
+
+ if (!m_surface->isActive() || m_surface->surfaceFormat() != m_presentEngine->videoSurfaceFormat()) {
+ m_surface->stop();
+ if (!m_surface->start(m_presentEngine->videoSurfaceFormat()))
+ return;
+ }
+
+ m_surface->present(frame);
+}
+
+void EVRCustomPresenter::positionChanged(qint64 position)
+{
+ m_positionOffset = position * 1000;
+}
+
+HRESULT setDesiredSampleTime(IMFSample *sample, const LONGLONG &sampleTime, const LONGLONG &duration)
+{
+ if (!sample)
+ return E_POINTER;
+
+ HRESULT hr = S_OK;
+ IMFDesiredSample *desired = NULL;
+
+ hr = sample->QueryInterface(IID_PPV_ARGS(&desired));
+ if (SUCCEEDED(hr))
+ desired->SetDesiredSampleTimeAndDuration(sampleTime, duration);
+
+ qt_evr_safe_release(&desired);
+ return hr;
+}
+
+HRESULT clearDesiredSampleTime(IMFSample *sample)
+{
+ if (!sample)
+ return E_POINTER;
+
+ HRESULT hr = S_OK;
+
+ IMFDesiredSample *desired = NULL;
+ IUnknown *unkSwapChain = NULL;
+
+ // We store some custom attributes on the sample, so we need to cache them
+ // and reset them.
+ //
+ // This works around the fact that IMFDesiredSample::Clear() removes all of the
+ // attributes from the sample.
+
+ UINT32 counter = MFGetAttributeUINT32(sample, MFSamplePresenter_SampleCounter, (UINT32)-1);
+
+ hr = sample->QueryInterface(IID_PPV_ARGS(&desired));
+ if (SUCCEEDED(hr)) {
+ desired->Clear();
+
+ hr = sample->SetUINT32(MFSamplePresenter_SampleCounter, counter);
+ if (FAILED(hr))
+ goto done;
+ }
+
+done:
+ qt_evr_safe_release(&unkSwapChain);
+ qt_evr_safe_release(&desired);
+ return hr;
+}
+
+HRESULT setMixerSourceRect(IMFTransform *mixer, const MFVideoNormalizedRect &sourceRect)
+{
+ if (!mixer)
+ return E_POINTER;
+
+ IMFAttributes *attributes = NULL;
+
+ HRESULT hr = mixer->GetAttributes(&attributes);
+ if (SUCCEEDED(hr)) {
+ hr = attributes->SetBlob(video_ZOOM_RECT, reinterpret_cast<const UINT8*>(&sourceRect),
+ sizeof(sourceRect));
+ attributes->Release();
+ }
+ return hr;
+}
+
+static QVideoFrame::PixelFormat pixelFormatFromMediaType(IMFMediaType *type)
+{
+ GUID majorType;
+ if (FAILED(type->GetMajorType(&majorType)))
+ return QVideoFrame::Format_Invalid;
+ if (majorType != MFMediaType_Video)
+ return QVideoFrame::Format_Invalid;
+
+ GUID subtype;
+ if (FAILED(type->GetGUID(MF_MT_SUBTYPE, &subtype)))
+ return QVideoFrame::Format_Invalid;
+
+ if (subtype == MFVideoFormat_RGB32)
+ return QVideoFrame::Format_RGB32;
+ if (subtype == MFVideoFormat_ARGB32)
+ return QVideoFrame::Format_ARGB32;
+ if (subtype == MFVideoFormat_RGB24)
+ return QVideoFrame::Format_RGB24;
+ if (subtype == MFVideoFormat_RGB565)
+ return QVideoFrame::Format_RGB565;
+ if (subtype == MFVideoFormat_RGB555)
+ return QVideoFrame::Format_RGB555;
+ if (subtype == MFVideoFormat_AYUV)
+ return QVideoFrame::Format_AYUV444;
+ if (subtype == MFVideoFormat_I420)
+ return QVideoFrame::Format_YUV420P;
+ if (subtype == MFVideoFormat_UYVY)
+ return QVideoFrame::Format_UYVY;
+ if (subtype == MFVideoFormat_YV12)
+ return QVideoFrame::Format_YV12;
+ if (subtype == MFVideoFormat_NV12)
+ return QVideoFrame::Format_NV12;
+
+ return QVideoFrame::Format_Invalid;
+}
+
+QT_END_NAMESPACE
diff --git a/src/multimedia/platform/wmf/evr/evrcustompresenter_p.h b/src/multimedia/platform/wmf/evr/evrcustompresenter_p.h
new file mode 100644
index 000000000..d60bc4d4c
--- /dev/null
+++ b/src/multimedia/platform/wmf/evr/evrcustompresenter_p.h
@@ -0,0 +1,388 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $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$
+**
+****************************************************************************/
+
+#ifndef EVRCUSTOMPRESENTER_H
+#define EVRCUSTOMPRESENTER_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QObject>
+#include <qmutex.h>
+#include <qqueue.h>
+#include <qevent.h>
+#include <qvideosurfaceformat.h>
+
+#include "evrdefs_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class EVRCustomPresenter;
+class D3DPresentEngine;
+
+class QAbstractVideoSurface;
+
+template<class T>
+class AsyncCallback : public IMFAsyncCallback
+{
+ Q_DISABLE_COPY(AsyncCallback)
+public:
+ typedef HRESULT (T::*InvokeFn)(IMFAsyncResult *asyncResult);
+
+ AsyncCallback(T *parent, InvokeFn fn) : m_parent(parent), m_invokeFn(fn)
+ {
+ }
+
+ // IUnknown
+ STDMETHODIMP QueryInterface(REFIID iid, void** ppv) override
+ {
+ 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() override {
+ // Delegate to parent class.
+ return m_parent->AddRef();
+ }
+ STDMETHODIMP_(ULONG) Release() override {
+ // Delegate to parent class.
+ return m_parent->Release();
+ }
+
+ // IMFAsyncCallback methods
+ STDMETHODIMP GetParameters(DWORD*, DWORD*) override
+ {
+ // Implementation of this method is optional.
+ return E_NOTIMPL;
+ }
+
+ STDMETHODIMP Invoke(IMFAsyncResult* asyncResult) override
+ {
+ return (m_parent->*m_invokeFn)(asyncResult);
+ }
+
+ T *m_parent;
+ InvokeFn m_invokeFn;
+};
+
+class Scheduler
+{
+ Q_DISABLE_COPY(Scheduler)
+public:
+ enum ScheduleEvent
+ {
+ Terminate = WM_USER,
+ Schedule = WM_USER + 1,
+ Flush = WM_USER + 2
+ };
+
+ Scheduler(EVRCustomPresenter *presenter);
+ ~Scheduler();
+
+ void setFrameRate(const MFRatio &fps);
+ void setClockRate(float rate) { m_playbackRate = rate; }
+
+ const LONGLONG &lastSampleTime() const { return m_lastSampleTime; }
+ const LONGLONG &frameDuration() const { return m_perFrameInterval; }
+
+ HRESULT startScheduler(IMFClock *clock);
+ HRESULT stopScheduler();
+
+ HRESULT scheduleSample(IMFSample *sample, bool presentNow);
+ HRESULT processSamplesInQueue(LONG *nextSleep);
+ HRESULT processSample(IMFSample *sample, LONG *nextSleep);
+ HRESULT flush();
+
+ bool areSamplesScheduled();
+
+ // ThreadProc for the scheduler thread.
+ static DWORD WINAPI schedulerThreadProc(LPVOID parameter);
+
+private:
+ DWORD schedulerThreadProcPrivate();
+
+ EVRCustomPresenter *m_presenter;
+
+ QQueue<IMFSample*> m_scheduledSamples; // Samples waiting to be presented.
+
+ IMFClock *m_clock; // Presentation clock. Can be NULL.
+
+ DWORD m_threadID;
+ HANDLE m_schedulerThread;
+ HANDLE m_threadReadyEvent;
+ HANDLE m_flushEvent;
+
+ float m_playbackRate;
+ MFTIME m_perFrameInterval; // Duration of each frame.
+ LONGLONG m_perFrame_1_4th; // 1/4th of the frame duration.
+ MFTIME m_lastSampleTime; // Most recent sample time.
+
+ QMutex m_mutex;
+};
+
+class SamplePool
+{
+ Q_DISABLE_COPY(SamplePool)
+public:
+ SamplePool();
+ ~SamplePool();
+
+ HRESULT initialize(QList<IMFSample*> &samples);
+ HRESULT clear();
+
+ HRESULT getSample(IMFSample **sample);
+ HRESULT returnSample(IMFSample *sample);
+
+private:
+ QMutex m_mutex;
+ QList<IMFSample*> m_videoSampleQueue;
+ bool m_initialized;
+};
+
+class EVRCustomPresenter
+ : public QObject
+ , public IMFVideoDeviceID
+ , public IMFVideoPresenter // Inherits IMFClockStateSink
+ , public IMFRateSupport
+ , public IMFGetService
+ , public IMFTopologyServiceLookupClient
+{
+ Q_DISABLE_COPY(EVRCustomPresenter)
+public:
+ // Defines the state of the presenter.
+ enum RenderState
+ {
+ RenderStarted = 1,
+ RenderStopped,
+ RenderPaused,
+ RenderShutdown // Initial state.
+ };
+
+ // Defines the presenter's state with respect to frame-stepping.
+ enum FrameStepState
+ {
+ FrameStepNone, // Not frame stepping.
+ FrameStepWaitingStart, // Frame stepping, but the clock is not started.
+ FrameStepPending, // Clock is started. Waiting for samples.
+ FrameStepScheduled, // Submitted a sample for rendering.
+ FrameStepComplete // Sample was rendered.
+ };
+
+ enum PresenterEvents
+ {
+ StartSurface = QEvent::User,
+ StopSurface = QEvent::User + 1,
+ PresentSample = QEvent::User + 2
+ };
+
+ EVRCustomPresenter(QAbstractVideoSurface *surface = 0);
+ ~EVRCustomPresenter() override;
+
+ bool isValid() const;
+
+ // IUnknown methods
+ STDMETHODIMP QueryInterface(REFIID riid, void ** ppv) override;
+ STDMETHODIMP_(ULONG) AddRef() override;
+ STDMETHODIMP_(ULONG) Release() override;
+
+ // IMFGetService methods
+ STDMETHODIMP GetService(REFGUID guidService, REFIID riid, LPVOID *ppvObject) override;
+
+ // IMFVideoPresenter methods
+ STDMETHODIMP ProcessMessage(MFVP_MESSAGE_TYPE message, ULONG_PTR param) override;
+ STDMETHODIMP GetCurrentMediaType(IMFVideoMediaType** mediaType) override;
+
+ // IMFClockStateSink methods
+ STDMETHODIMP OnClockStart(MFTIME systemTime, LONGLONG clockStartOffset) override;
+ STDMETHODIMP OnClockStop(MFTIME systemTime) override;
+ STDMETHODIMP OnClockPause(MFTIME systemTime) override;
+ STDMETHODIMP OnClockRestart(MFTIME systemTime) override;
+ STDMETHODIMP OnClockSetRate(MFTIME systemTime, float rate) override;
+
+ // IMFRateSupport methods
+ STDMETHODIMP GetSlowestRate(MFRATE_DIRECTION direction, BOOL thin, float *rate) override;
+ STDMETHODIMP GetFastestRate(MFRATE_DIRECTION direction, BOOL thin, float *rate) override;
+ STDMETHODIMP IsRateSupported(BOOL thin, float rate, float *nearestSupportedRate) override;
+
+ // IMFVideoDeviceID methods
+ STDMETHODIMP GetDeviceID(IID* deviceID) override;
+
+ // IMFTopologyServiceLookupClient methods
+ STDMETHODIMP InitServicePointers(IMFTopologyServiceLookup *lookup) override;
+ STDMETHODIMP ReleaseServicePointers() override;
+
+ void supportedFormatsChanged();
+ void setSurface(QAbstractVideoSurface *surface);
+
+ void startSurface();
+ void stopSurface();
+ void presentSample(IMFSample *sample);
+
+ bool event(QEvent *) override;
+
+public Q_SLOTS:
+ void positionChanged(qint64 position);
+
+private:
+ HRESULT checkShutdown() const
+ {
+ if (m_renderState == RenderShutdown)
+ return MF_E_SHUTDOWN;
+ else
+ return S_OK;
+ }
+
+ // The "active" state is started or paused.
+ inline bool isActive() const
+ {
+ return ((m_renderState == RenderStarted) || (m_renderState == RenderPaused));
+ }
+
+ // Scrubbing occurs when the frame rate is 0.
+ inline bool isScrubbing() const { return m_playbackRate == 0.0f; }
+
+ // Send an event to the EVR through its IMediaEventSink interface.
+ void notifyEvent(long eventCode, LONG_PTR param1, LONG_PTR param2)
+ {
+ if (m_mediaEventSink)
+ m_mediaEventSink->Notify(eventCode, param1, param2);
+ }
+
+ float getMaxRate(bool thin);
+
+ // Mixer operations
+ HRESULT configureMixer(IMFTransform *mixer);
+
+ // Formats
+ HRESULT createOptimalVideoType(IMFMediaType* proposed, IMFMediaType **optimal);
+ HRESULT setMediaType(IMFMediaType *mediaType);
+ HRESULT isMediaTypeSupported(IMFMediaType *mediaType);
+
+ // Message handlers
+ HRESULT flush();
+ HRESULT renegotiateMediaType();
+ HRESULT processInputNotify();
+ HRESULT beginStreaming();
+ HRESULT endStreaming();
+ HRESULT checkEndOfStream();
+
+ // Managing samples
+ void processOutputLoop();
+ HRESULT processOutput();
+ HRESULT deliverSample(IMFSample *sample, bool repaint);
+ HRESULT trackSample(IMFSample *sample);
+ void releaseResources();
+
+ // Frame-stepping
+ HRESULT prepareFrameStep(DWORD steps);
+ HRESULT startFrameStep();
+ HRESULT deliverFrameStepSample(IMFSample *sample);
+ HRESULT completeFrameStep(IMFSample *sample);
+ HRESULT cancelFrameStep();
+
+ // Callback when a video sample is released.
+ HRESULT onSampleFree(IMFAsyncResult *result);
+ AsyncCallback<EVRCustomPresenter> m_sampleFreeCB;
+
+ // Holds information related to frame-stepping.
+ struct FrameStep
+ {
+ FrameStepState state = FrameStepNone;
+ QList<IMFSample*> samples;
+ DWORD steps = 0;
+ DWORD_PTR sampleNoRef = 0;
+ };
+
+ long m_refCount;
+
+ RenderState m_renderState;
+ FrameStep m_frameStep;
+
+ QRecursiveMutex m_mutex;
+
+ // Samples and scheduling
+ Scheduler m_scheduler; // Manages scheduling of samples.
+ SamplePool m_samplePool; // Pool of allocated samples.
+ DWORD m_tokenCounter; // Counter. Incremented whenever we create new samples.
+
+ // Rendering state
+ bool m_sampleNotify; // Did the mixer signal it has an input sample?
+ bool m_repaint; // Do we need to repaint the last sample?
+ bool m_prerolled; // Have we presented at least one sample?
+ bool m_endStreaming; // Did we reach the end of the stream (EOS)?
+
+ MFVideoNormalizedRect m_sourceRect;
+ float m_playbackRate;
+
+ D3DPresentEngine *m_presentEngine; // Rendering engine. (Never null if the constructor succeeds.)
+
+ IMFClock *m_clock; // The EVR's clock.
+ IMFTransform *m_mixer; // The EVR's mixer.
+ IMediaEventSink *m_mediaEventSink; // The EVR's event-sink interface.
+ IMFMediaType *m_mediaType; // Output media type
+
+ QAbstractVideoSurface *m_surface;
+ bool m_canRenderToSurface;
+ qint64 m_positionOffset; // Seek position in microseconds.
+};
+
+bool qt_evr_setCustomPresenter(IUnknown *evr, EVRCustomPresenter *presenter);
+
+QT_END_NAMESPACE
+
+#endif // EVRCUSTOMPRESENTER_H
diff --git a/src/multimedia/platform/wmf/evr/evrd3dpresentengine.cpp b/src/multimedia/platform/wmf/evr/evrd3dpresentengine.cpp
new file mode 100644
index 000000000..8cb6d2593
--- /dev/null
+++ b/src/multimedia/platform/wmf/evr/evrd3dpresentengine.cpp
@@ -0,0 +1,412 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $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 "evrd3dpresentengine_p.h"
+
+#include "evrhelpers_p.h"
+
+#include <qabstractvideobuffer.h>
+#include <QAbstractVideoSurface>
+#include <qvideoframe.h>
+#include <QDebug>
+#include <qthread.h>
+#include <QOffscreenSurface>
+
+static const int PRESENTER_BUFFER_COUNT = 3;
+
+QT_BEGIN_NAMESPACE
+
+class IMFSampleVideoBuffer: public QAbstractVideoBuffer
+{
+public:
+ IMFSampleVideoBuffer(D3DPresentEngine *engine, IMFSample *sample, QAbstractVideoBuffer::HandleType handleType)
+ : QAbstractVideoBuffer(handleType)
+ , m_engine(engine)
+ , m_sample(sample)
+ , m_surface(0)
+ , m_mapMode(NotMapped)
+ {
+ if (m_sample) {
+ m_sample->AddRef();
+
+ IMFMediaBuffer *buffer;
+ if (SUCCEEDED(m_sample->GetBufferByIndex(0, &buffer))) {
+ MFGetService(buffer,
+ mr_BUFFER_SERVICE,
+ iid_IDirect3DSurface9,
+ reinterpret_cast<void **>(&m_surface));
+ buffer->Release();
+ }
+ }
+ }
+
+ ~IMFSampleVideoBuffer() override
+ {
+ if (m_surface) {
+ if (m_mapMode != NotMapped)
+ m_surface->UnlockRect();
+ m_surface->Release();
+ }
+ if (m_sample)
+ m_sample->Release();
+ }
+
+ QVariant handle() const override;
+
+ MapMode mapMode() const override { return m_mapMode; }
+ MapData map(MapMode mode) override;
+ void unmap() override;
+
+private:
+ mutable D3DPresentEngine *m_engine;
+ IMFSample *m_sample;
+ IDirect3DSurface9 *m_surface;
+ MapMode m_mapMode;
+ mutable unsigned int m_textureId = 0;
+};
+
+IMFSampleVideoBuffer::MapData IMFSampleVideoBuffer::map(MapMode mode)
+{
+ if (!m_surface || m_mapMode != NotMapped)
+ return {};
+
+ D3DSURFACE_DESC desc;
+ if (FAILED(m_surface->GetDesc(&desc)))
+ return {};
+
+ D3DLOCKED_RECT rect;
+ if (FAILED(m_surface->LockRect(&rect, NULL, mode == ReadOnly ? D3DLOCK_READONLY : 0)))
+ return {};
+
+ m_mapMode = mode;
+
+ MapData mapData;
+ mapData.nBytes = (int)(rect.Pitch * desc.Height);
+ mapData.nPlanes = 1;
+ mapData.bytesPerLine[0] = (int)rect.Pitch;
+ mapData.data[0] = reinterpret_cast<uchar *>(rect.pBits);
+ return mapData;
+}
+
+void IMFSampleVideoBuffer::unmap()
+{
+ if (m_mapMode == NotMapped)
+ return;
+
+ m_mapMode = NotMapped;
+ m_surface->UnlockRect();
+}
+
+QVariant IMFSampleVideoBuffer::handle() const
+{
+ return m_textureId;
+}
+
+
+D3DPresentEngine::D3DPresentEngine()
+ : m_deviceResetToken(0)
+ , m_D3D9(0)
+ , m_device(0)
+ , m_deviceManager(0)
+ , m_useTextureRendering(false)
+{
+ ZeroMemory(&m_displayMode, sizeof(m_displayMode));
+
+ HRESULT hr = initializeD3D();
+
+ if (SUCCEEDED(hr)) {
+ hr = createD3DDevice();
+ if (FAILED(hr))
+ qWarning("Failed to create D3D device");
+ } else {
+ qWarning("Failed to initialize D3D");
+ }
+}
+
+D3DPresentEngine::~D3DPresentEngine()
+{
+ releaseResources();
+
+ qt_evr_safe_release(&m_device);
+ qt_evr_safe_release(&m_deviceManager);
+ qt_evr_safe_release(&m_D3D9);
+}
+
+HRESULT D3DPresentEngine::initializeD3D()
+{
+ HRESULT hr = Direct3DCreate9Ex(D3D_SDK_VERSION, &m_D3D9);
+
+ if (SUCCEEDED(hr))
+ hr = DXVA2CreateDirect3DDeviceManager9(&m_deviceResetToken, &m_deviceManager);
+
+ return hr;
+}
+
+HRESULT D3DPresentEngine::createD3DDevice()
+{
+ HRESULT hr = S_OK;
+ HWND hwnd = NULL;
+ UINT uAdapterID = D3DADAPTER_DEFAULT;
+ DWORD vp = 0;
+
+ D3DCAPS9 ddCaps;
+ ZeroMemory(&ddCaps, sizeof(ddCaps));
+
+ IDirect3DDevice9Ex* device = NULL;
+
+ if (!m_D3D9 || !m_deviceManager)
+ return MF_E_NOT_INITIALIZED;
+
+ hwnd = ::GetShellWindow();
+
+ D3DPRESENT_PARAMETERS pp;
+ ZeroMemory(&pp, sizeof(pp));
+
+ pp.BackBufferWidth = 1;
+ pp.BackBufferHeight = 1;
+ pp.BackBufferFormat = D3DFMT_UNKNOWN;
+ pp.BackBufferCount = 1;
+ pp.Windowed = TRUE;
+ pp.SwapEffect = D3DSWAPEFFECT_DISCARD;
+ pp.BackBufferFormat = D3DFMT_UNKNOWN;
+ pp.hDeviceWindow = hwnd;
+ pp.Flags = D3DPRESENTFLAG_VIDEO;
+ pp.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;
+
+ hr = m_D3D9->GetDeviceCaps(uAdapterID, D3DDEVTYPE_HAL, &ddCaps);
+ if (FAILED(hr))
+ goto done;
+
+ if (ddCaps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT)
+ vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;
+ else
+ vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
+
+ hr = m_D3D9->CreateDeviceEx(
+ uAdapterID,
+ D3DDEVTYPE_HAL,
+ pp.hDeviceWindow,
+ vp | D3DCREATE_NOWINDOWCHANGES | D3DCREATE_MULTITHREADED | D3DCREATE_FPU_PRESERVE,
+ &pp,
+ NULL,
+ &device
+ );
+ if (FAILED(hr))
+ goto done;
+
+ hr = m_D3D9->GetAdapterDisplayMode(uAdapterID, &m_displayMode);
+ if (FAILED(hr))
+ goto done;
+
+ hr = m_deviceManager->ResetDevice(device, m_deviceResetToken);
+ if (FAILED(hr))
+ goto done;
+
+ qt_evr_safe_release(&m_device);
+
+ m_device = device;
+ m_device->AddRef();
+
+done:
+ qt_evr_safe_release(&device);
+ return hr;
+}
+
+bool D3DPresentEngine::isValid() const
+{
+ return m_device != NULL;
+}
+
+void D3DPresentEngine::releaseResources()
+{
+ m_surfaceFormat = QVideoSurfaceFormat();
+}
+
+HRESULT D3DPresentEngine::getService(REFGUID, REFIID riid, void** ppv)
+{
+ HRESULT hr = S_OK;
+
+ if (riid == __uuidof(IDirect3DDeviceManager9)) {
+ if (m_deviceManager == NULL) {
+ hr = MF_E_UNSUPPORTED_SERVICE;
+ } else {
+ *ppv = m_deviceManager;
+ m_deviceManager->AddRef();
+ }
+ } else {
+ hr = MF_E_UNSUPPORTED_SERVICE;
+ }
+
+ return hr;
+}
+
+HRESULT D3DPresentEngine::checkFormat(D3DFORMAT format)
+{
+ if (!m_D3D9 || !m_device)
+ return E_FAIL;
+
+ HRESULT hr = S_OK;
+
+ D3DDISPLAYMODE mode;
+ D3DDEVICE_CREATION_PARAMETERS params;
+
+ hr = m_device->GetCreationParameters(&params);
+ if (FAILED(hr))
+ return hr;
+
+ UINT uAdapter = params.AdapterOrdinal;
+ D3DDEVTYPE type = params.DeviceType;
+
+ hr = m_D3D9->GetAdapterDisplayMode(uAdapter, &mode);
+ if (FAILED(hr))
+ return hr;
+
+ hr = m_D3D9->CheckDeviceFormat(uAdapter, type, mode.Format,
+ D3DUSAGE_RENDERTARGET,
+ D3DRTYPE_SURFACE,
+ format);
+
+ if (m_useTextureRendering && format != D3DFMT_X8R8G8B8 && format != D3DFMT_A8R8G8B8) {
+ // The texture is always in RGB32 so the d3d driver must support conversion from the
+ // requested format to RGB32.
+ hr = m_D3D9->CheckDeviceFormatConversion(uAdapter, type, format, D3DFMT_X8R8G8B8);
+ }
+
+ return hr;
+}
+
+bool D3DPresentEngine::supportsTextureRendering() const
+{
+ return false;
+}
+
+void D3DPresentEngine::setHint(Hint hint, bool enable)
+{
+ if (hint == RenderToTexture)
+ m_useTextureRendering = enable && supportsTextureRendering();
+}
+
+HRESULT D3DPresentEngine::createVideoSamples(IMFMediaType *format, QList<IMFSample*> &videoSampleQueue)
+{
+ if (!format)
+ return MF_E_UNEXPECTED;
+
+ HRESULT hr = S_OK;
+
+ IDirect3DSurface9 *surface = NULL;
+ IMFSample *videoSample = NULL;
+
+ releaseResources();
+
+ UINT32 width = 0, height = 0;
+ hr = MFGetAttributeSize(format, MF_MT_FRAME_SIZE, &width, &height);
+ if (FAILED(hr))
+ return hr;
+
+ DWORD d3dFormat = 0;
+ hr = qt_evr_getFourCC(format, &d3dFormat);
+ if (FAILED(hr))
+ return hr;
+
+ // Create the video samples.
+ for (int i = 0; i < PRESENTER_BUFFER_COUNT; i++) {
+ hr = m_device->CreateRenderTarget(width, height,
+ (D3DFORMAT)d3dFormat,
+ D3DMULTISAMPLE_NONE,
+ 0,
+ TRUE,
+ &surface, NULL);
+ if (FAILED(hr))
+ goto done;
+
+ hr = MFCreateVideoSampleFromSurface(surface, &videoSample);
+ if (FAILED(hr))
+ goto done;
+
+ videoSample->AddRef();
+ videoSampleQueue.append(videoSample);
+
+ qt_evr_safe_release(&videoSample);
+ qt_evr_safe_release(&surface);
+ }
+
+done:
+ if (SUCCEEDED(hr)) {
+ m_surfaceFormat = QVideoSurfaceFormat(QSize(width, height),
+ m_useTextureRendering ? QVideoFrame::Format_RGB32
+ : qt_evr_pixelFormatFromD3DFormat(d3dFormat),
+ m_useTextureRendering ? QAbstractVideoBuffer::GLTextureHandle
+ : QAbstractVideoBuffer::NoHandle);
+ UINT32 horizontal = 1, vertical = 1;
+ hr = MFGetAttributeRatio(format, MF_MT_PIXEL_ASPECT_RATIO, &horizontal, &vertical);
+ if (SUCCEEDED(hr))
+ m_surfaceFormat.setPixelAspectRatio(horizontal, vertical);
+ } else {
+ releaseResources();
+ }
+
+ qt_evr_safe_release(&videoSample);
+ qt_evr_safe_release(&surface);
+ return hr;
+}
+
+QVideoFrame D3DPresentEngine::makeVideoFrame(IMFSample *sample)
+{
+ if (!sample)
+ return QVideoFrame();
+
+ QVideoFrame frame(new IMFSampleVideoBuffer(this, sample, m_surfaceFormat.handleType()),
+ m_surfaceFormat.frameSize(),
+ m_surfaceFormat.pixelFormat());
+
+ // WMF uses 100-nanosecond units, Qt uses microseconds
+ LONGLONG startTime = 0;
+ auto hr = sample->GetSampleTime(&startTime);
+ if (SUCCEEDED(hr)) {
+ frame.setStartTime(startTime * 0.1);
+
+ LONGLONG duration = -1;
+ if (SUCCEEDED(sample->GetSampleDuration(&duration)))
+ frame.setEndTime((startTime + duration) * 0.1);
+ }
+
+ return frame;
+}
+
+QT_END_NAMESPACE
diff --git a/src/multimedia/platform/wmf/evr/evrd3dpresentengine_p.h b/src/multimedia/platform/wmf/evr/evrd3dpresentengine_p.h
new file mode 100644
index 000000000..1e70feadb
--- /dev/null
+++ b/src/multimedia/platform/wmf/evr/evrd3dpresentengine_p.h
@@ -0,0 +1,163 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $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$
+**
+****************************************************************************/
+
+#ifndef EVRD3DPRESENTENGINE_H
+#define EVRD3DPRESENTENGINE_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QMutex>
+#include <QVideoSurfaceFormat>
+
+#include <d3d9.h>
+
+struct IDirect3D9Ex;
+struct IDirect3DDevice9Ex;
+struct IDirect3DDeviceManager9;
+struct IDirect3DSurface9;
+struct IDirect3DTexture9;
+struct IMFSample;
+struct IMFMediaType;
+
+// Randomly generated GUIDs
+static const GUID MFSamplePresenter_SampleCounter =
+{ 0xb0bb83cc, 0xf10f, 0x4e2e, { 0xaa, 0x2b, 0x29, 0xea, 0x5e, 0x92, 0xef, 0x85 } };
+
+QT_BEGIN_NAMESPACE
+
+class QAbstractVideoSurface;
+
+#ifdef MAYBE_ANGLE
+
+class OpenGLResources;
+
+class EGLWrapper
+{
+ Q_DISABLE_COPY(EGLWrapper)
+public:
+ EGLWrapper();
+
+ __eglMustCastToProperFunctionPointerType getProcAddress(const char *procname);
+ EGLSurface createPbufferSurface(EGLDisplay dpy, EGLConfig config, const EGLint *attrib_list);
+ EGLBoolean destroySurface(EGLDisplay dpy, EGLSurface surface);
+ EGLBoolean bindTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer);
+ EGLBoolean releaseTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer);
+
+private:
+ typedef __eglMustCastToProperFunctionPointerType (EGLAPIENTRYP EglGetProcAddress)(const char *procname);
+ typedef EGLSurface (EGLAPIENTRYP EglCreatePbufferSurface)(EGLDisplay dpy, EGLConfig config, const EGLint *attrib_list);
+ typedef EGLBoolean (EGLAPIENTRYP EglDestroySurface)(EGLDisplay dpy, EGLSurface surface);
+ typedef EGLBoolean (EGLAPIENTRYP EglBindTexImage)(EGLDisplay dpy, EGLSurface surface, EGLint buffer);
+ typedef EGLBoolean (EGLAPIENTRYP EglReleaseTexImage)(EGLDisplay dpy, EGLSurface surface, EGLint buffer);
+
+ EglGetProcAddress m_eglGetProcAddress;
+ EglCreatePbufferSurface m_eglCreatePbufferSurface;
+ EglDestroySurface m_eglDestroySurface;
+ EglBindTexImage m_eglBindTexImage;
+ EglReleaseTexImage m_eglReleaseTexImage;
+};
+
+#endif // MAYBE_ANGLE
+
+class D3DPresentEngine
+{
+ Q_DISABLE_COPY(D3DPresentEngine)
+public:
+ enum Hint
+ {
+ RenderToTexture
+ };
+
+ D3DPresentEngine();
+ virtual ~D3DPresentEngine();
+
+ bool isValid() const;
+ void setHint(Hint hint, bool enable = true);
+
+ HRESULT getService(REFGUID guidService, REFIID riid, void** ppv);
+ HRESULT checkFormat(D3DFORMAT format);
+ UINT refreshRate() const { return m_displayMode.RefreshRate; }
+
+ bool supportsTextureRendering() const;
+ bool isTextureRenderingEnabled() const { return m_useTextureRendering; }
+
+ HRESULT createVideoSamples(IMFMediaType *format, QList<IMFSample*>& videoSampleQueue);
+ QVideoSurfaceFormat videoSurfaceFormat() const { return m_surfaceFormat; }
+ QVideoFrame makeVideoFrame(IMFSample* sample);
+
+ void releaseResources();
+
+private:
+ HRESULT initializeD3D();
+ HRESULT createD3DDevice();
+
+
+ UINT m_deviceResetToken;
+ D3DDISPLAYMODE m_displayMode;
+
+ IDirect3D9Ex *m_D3D9;
+ IDirect3DDevice9Ex *m_device;
+ IDirect3DDeviceManager9 *m_deviceManager;
+
+ QVideoSurfaceFormat m_surfaceFormat;
+
+ bool m_useTextureRendering;
+
+#ifdef MAYBE_ANGLE
+ unsigned int updateTexture(IDirect3DSurface9 *src);
+
+ OpenGLResources *m_glResources;
+ IDirect3DTexture9 *m_texture;
+#endif
+
+ friend class IMFSampleVideoBuffer;
+};
+
+QT_END_NAMESPACE
+
+#endif // EVRD3DPRESENTENGINE_H
diff --git a/src/multimedia/platform/wmf/evr/evrdefs.cpp b/src/multimedia/platform/wmf/evr/evrdefs.cpp
new file mode 100644
index 000000000..94370a14a
--- /dev/null
+++ b/src/multimedia/platform/wmf/evr/evrdefs.cpp
@@ -0,0 +1,48 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $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 "evrdefs_p.h"
+
+const CLSID clsid_EnhancedVideoRenderer = { 0xfa10746c, 0x9b63, 0x4b6c, {0xbc, 0x49, 0xfc, 0x30, 0xe, 0xa5, 0xf2, 0x56} };
+const GUID mr_VIDEO_RENDER_SERVICE = { 0x1092a86c, 0xab1a, 0x459a, {0xa3, 0x36, 0x83, 0x1f, 0xbc, 0x4d, 0x11, 0xff} };
+const GUID mr_VIDEO_MIXER_SERVICE = { 0x73cd2fc, 0x6cf4, 0x40b7, {0x88, 0x59, 0xe8, 0x95, 0x52, 0xc8, 0x41, 0xf8} };
+const GUID mr_BUFFER_SERVICE = { 0xa562248c, 0x9ac6, 0x4ffc, {0x9f, 0xba, 0x3a, 0xf8, 0xf8, 0xad, 0x1a, 0x4d} };
+const GUID video_ZOOM_RECT = { 0x7aaa1638, 0x1b7f, 0x4c93, {0xbd, 0x89, 0x5b, 0x9c, 0x9f, 0xb6, 0xfc, 0xf0} };
+const GUID iid_IDirect3DDevice9 = { 0xd0223b96, 0xbf7a, 0x43fd, {0x92, 0xbd, 0xa4, 0x3b, 0xd, 0x82, 0xb9, 0xeb} };
+const GUID iid_IDirect3DSurface9 = { 0xcfbaf3a, 0x9ff6, 0x429a, {0x99, 0xb3, 0xa2, 0x79, 0x6a, 0xf8, 0xb8, 0x9b} };
diff --git a/src/multimedia/platform/wmf/evr/evrdefs_p.h b/src/multimedia/platform/wmf/evr/evrdefs_p.h
new file mode 100644
index 000000000..f9df48387
--- /dev/null
+++ b/src/multimedia/platform/wmf/evr/evrdefs_p.h
@@ -0,0 +1,364 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $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$
+**
+****************************************************************************/
+
+#ifndef EVRDEFS_H
+#define EVRDEFS_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <d3d9.h>
+#include <evr9.h>
+#include <evr.h>
+#include <dxva2api.h>
+#include <mfapi.h>
+#include <mfidl.h>
+#include <mferror.h>
+
+extern const CLSID clsid_EnhancedVideoRenderer;
+extern const GUID mr_VIDEO_RENDER_SERVICE;
+extern const GUID mr_VIDEO_MIXER_SERVICE;
+extern const GUID mr_BUFFER_SERVICE;
+extern const GUID video_ZOOM_RECT;
+extern const GUID iid_IDirect3DDevice9;
+extern const GUID iid_IDirect3DSurface9;
+
+// The following is required to compile with MinGW
+
+extern "C" {
+HRESULT WINAPI MFCreateVideoSampleFromSurface(IUnknown *pUnkSurface, IMFSample **ppSample);
+HRESULT WINAPI Direct3DCreate9Ex(UINT SDKVersion, IDirect3D9Ex**);
+}
+
+#ifndef PRESENTATION_CURRENT_POSITION
+#define PRESENTATION_CURRENT_POSITION 0x7fffffffffffffff
+#endif
+
+#ifndef MF_E_SHUTDOWN
+#define MF_E_SHUTDOWN ((HRESULT)0xC00D3E85L)
+#endif
+
+#ifndef MF_E_SAMPLEALLOCATOR_EMPTY
+#define MF_E_SAMPLEALLOCATOR_EMPTY ((HRESULT)0xC00D4A3EL)
+#endif
+
+#ifndef MF_E_TRANSFORM_STREAM_CHANGE
+#define MF_E_TRANSFORM_STREAM_CHANGE ((HRESULT)0xC00D6D61L)
+#endif
+
+#ifndef MF_E_TRANSFORM_NEED_MORE_INPUT
+#define MF_E_TRANSFORM_NEED_MORE_INPUT ((HRESULT)0xC00D6D72L)
+#endif
+
+#if defined(__GNUC__) && !defined(_MFVideoNormalizedRect_)
+#define _MFVideoNormalizedRect_
+typedef struct MFVideoNormalizedRect {
+ float left;
+ float top;
+ float right;
+ float bottom;
+} MFVideoNormalizedRect;
+#endif
+
+#include <initguid.h>
+
+#ifndef __IMFGetService_INTERFACE_DEFINED__
+#define __IMFGetService_INTERFACE_DEFINED__
+DEFINE_GUID(IID_IMFGetService, 0xfa993888, 0x4383, 0x415a, 0xa9,0x30, 0xdd,0x47,0x2a,0x8c,0xf6,0xf7);
+MIDL_INTERFACE("fa993888-4383-415a-a930-dd472a8cf6f7")
+IMFGetService : public IUnknown
+{
+ virtual HRESULT STDMETHODCALLTYPE GetService(REFGUID, REFIID, LPVOID *) = 0;
+};
+#ifdef __CRT_UUID_DECL
+__CRT_UUID_DECL(IMFGetService, 0xfa993888, 0x4383, 0x415a, 0xa9,0x30, 0xdd,0x47,0x2a,0x8c,0xf6,0xf7)
+#endif
+#endif // __IMFGetService_INTERFACE_DEFINED__
+
+#ifndef __IMFVideoDisplayControl_INTERFACE_DEFINED__
+#define __IMFVideoDisplayControl_INTERFACE_DEFINED__
+typedef enum MFVideoAspectRatioMode
+{
+ MFVideoARMode_None = 0,
+ MFVideoARMode_PreservePicture = 0x1,
+ MFVideoARMode_PreservePixel = 0x2,
+ MFVideoARMode_NonLinearStretch = 0x4,
+ MFVideoARMode_Mask = 0x7
+} MFVideoAspectRatioMode;
+
+DEFINE_GUID(IID_IMFVideoDisplayControl, 0xa490b1e4, 0xab84, 0x4d31, 0xa1,0xb2, 0x18,0x1e,0x03,0xb1,0x07,0x7a);
+MIDL_INTERFACE("a490b1e4-ab84-4d31-a1b2-181e03b1077a")
+IMFVideoDisplayControl : public IUnknown
+{
+ virtual HRESULT STDMETHODCALLTYPE GetNativeVideoSize(SIZE *, SIZE *) = 0;
+ virtual HRESULT STDMETHODCALLTYPE GetIdealVideoSize(SIZE *, SIZE *) = 0;
+ virtual HRESULT STDMETHODCALLTYPE SetVideoPosition(const MFVideoNormalizedRect *, const LPRECT) = 0;
+ virtual HRESULT STDMETHODCALLTYPE GetVideoPosition(MFVideoNormalizedRect *, LPRECT) = 0;
+ virtual HRESULT STDMETHODCALLTYPE SetAspectRatioMode(DWORD) = 0;
+ virtual HRESULT STDMETHODCALLTYPE GetAspectRatioMode(DWORD *) = 0;
+ virtual HRESULT STDMETHODCALLTYPE SetVideoWindow(HWND) = 0;
+ virtual HRESULT STDMETHODCALLTYPE GetVideoWindow(HWND *) = 0;
+ virtual HRESULT STDMETHODCALLTYPE RepaintVideo(void) = 0;
+ virtual HRESULT STDMETHODCALLTYPE GetCurrentImage(BITMAPINFOHEADER *, BYTE **, DWORD *, LONGLONG *) = 0;
+ virtual HRESULT STDMETHODCALLTYPE SetBorderColor(COLORREF) = 0;
+ virtual HRESULT STDMETHODCALLTYPE GetBorderColor(COLORREF *) = 0;
+ virtual HRESULT STDMETHODCALLTYPE SetRenderingPrefs(DWORD) = 0;
+ virtual HRESULT STDMETHODCALLTYPE GetRenderingPrefs(DWORD *) = 0;
+ virtual HRESULT STDMETHODCALLTYPE SetFullscreen(BOOL) = 0;
+ virtual HRESULT STDMETHODCALLTYPE GetFullscreen(BOOL *) = 0;
+};
+#ifdef __CRT_UUID_DECL
+__CRT_UUID_DECL(IMFVideoDisplayControl, 0xa490b1e4, 0xab84, 0x4d31, 0xa1,0xb2, 0x18,0x1e,0x03,0xb1,0x07,0x7a)
+#endif
+#endif // __IMFVideoDisplayControl_INTERFACE_DEFINED__
+
+#ifndef __IMFVideoProcessor_INTERFACE_DEFINED__
+#define __IMFVideoProcessor_INTERFACE_DEFINED__
+DEFINE_GUID(IID_IMFVideoProcessor, 0x6AB0000C, 0xFECE, 0x4d1f, 0xA2,0xAC, 0xA9,0x57,0x35,0x30,0x65,0x6E);
+MIDL_INTERFACE("6AB0000C-FECE-4d1f-A2AC-A9573530656E")
+IMFVideoProcessor : public IUnknown
+{
+ virtual HRESULT STDMETHODCALLTYPE GetAvailableVideoProcessorModes(UINT *, GUID **) = 0;
+ virtual HRESULT STDMETHODCALLTYPE GetVideoProcessorCaps(LPGUID, DXVA2_VideoProcessorCaps *) = 0;
+ virtual HRESULT STDMETHODCALLTYPE GetVideoProcessorMode(LPGUID) = 0;
+ virtual HRESULT STDMETHODCALLTYPE SetVideoProcessorMode(LPGUID) = 0;
+ virtual HRESULT STDMETHODCALLTYPE GetProcAmpRange(DWORD, DXVA2_ValueRange *) = 0;
+ virtual HRESULT STDMETHODCALLTYPE GetProcAmpValues(DWORD, DXVA2_ProcAmpValues *) = 0;
+ virtual HRESULT STDMETHODCALLTYPE SetProcAmpValues(DWORD, DXVA2_ProcAmpValues *) = 0;
+ virtual HRESULT STDMETHODCALLTYPE GetFilteringRange(DWORD, DXVA2_ValueRange *) = 0;
+ virtual HRESULT STDMETHODCALLTYPE GetFilteringValue(DWORD, DXVA2_Fixed32 *) = 0;
+ virtual HRESULT STDMETHODCALLTYPE SetFilteringValue(DWORD, DXVA2_Fixed32 *) = 0;
+ virtual HRESULT STDMETHODCALLTYPE GetBackgroundColor(COLORREF *) = 0;
+ virtual HRESULT STDMETHODCALLTYPE SetBackgroundColor(COLORREF) = 0;
+};
+#ifdef __CRT_UUID_DECL
+__CRT_UUID_DECL(IMFVideoProcessor, 0x6AB0000C, 0xFECE, 0x4d1f, 0xA2,0xAC, 0xA9,0x57,0x35,0x30,0x65,0x6E)
+#endif
+#endif // __IMFVideoProcessor_INTERFACE_DEFINED__
+
+#ifndef __IMFVideoDeviceID_INTERFACE_DEFINED__
+#define __IMFVideoDeviceID_INTERFACE_DEFINED__
+DEFINE_GUID(IID_IMFVideoDeviceID, 0xA38D9567, 0x5A9C, 0x4f3c, 0xB2,0x93, 0x8E,0xB4,0x15,0xB2,0x79,0xBA);
+MIDL_INTERFACE("A38D9567-5A9C-4f3c-B293-8EB415B279BA")
+IMFVideoDeviceID : public IUnknown
+{
+public:
+ virtual HRESULT STDMETHODCALLTYPE GetDeviceID(IID *pDeviceID) = 0;
+};
+#ifdef __CRT_UUID_DECL
+__CRT_UUID_DECL(IMFVideoDeviceID, 0xA38D9567, 0x5A9C, 0x4f3c, 0xB2,0x93, 0x8E,0xB4,0x15,0xB2,0x79,0xBA)
+#endif
+#endif // __IMFVideoDeviceID_INTERFACE_DEFINED__
+
+#ifndef __IMFClockStateSink_INTERFACE_DEFINED__
+#define __IMFClockStateSink_INTERFACE_DEFINED__
+DEFINE_GUID(IID_IMFClockStateSink, 0xF6696E82, 0x74F7, 0x4f3d, 0xA1,0x78, 0x8A,0x5E,0x09,0xC3,0x65,0x9F);
+MIDL_INTERFACE("F6696E82-74F7-4f3d-A178-8A5E09C3659F")
+IMFClockStateSink : public IUnknown
+{
+public:
+ virtual HRESULT STDMETHODCALLTYPE OnClockStart(MFTIME hnsSystemTime, LONGLONG llClockStartOffset) = 0;
+ virtual HRESULT STDMETHODCALLTYPE OnClockStop(MFTIME hnsSystemTime) = 0;
+ virtual HRESULT STDMETHODCALLTYPE OnClockPause(MFTIME hnsSystemTime) = 0;
+ virtual HRESULT STDMETHODCALLTYPE OnClockRestart(MFTIME hnsSystemTime) = 0;
+ virtual HRESULT STDMETHODCALLTYPE OnClockSetRate(MFTIME hnsSystemTime, float flRate) = 0;
+};
+#ifdef __CRT_UUID_DECL
+__CRT_UUID_DECL(IMFClockStateSink, 0xF6696E82, 0x74F7, 0x4f3d, 0xA1,0x78, 0x8A,0x5E,0x09,0xC3,0x65,0x9F)
+#endif
+#endif // __IMFClockStateSink_INTERFACE_DEFINED__
+
+#ifndef __IMFVideoPresenter_INTERFACE_DEFINED__
+#define __IMFVideoPresenter_INTERFACE_DEFINED__
+typedef enum MFVP_MESSAGE_TYPE
+{
+ MFVP_MESSAGE_FLUSH = 0,
+ MFVP_MESSAGE_INVALIDATEMEDIATYPE = 0x1,
+ MFVP_MESSAGE_PROCESSINPUTNOTIFY = 0x2,
+ MFVP_MESSAGE_BEGINSTREAMING = 0x3,
+ MFVP_MESSAGE_ENDSTREAMING = 0x4,
+ MFVP_MESSAGE_ENDOFSTREAM = 0x5,
+ MFVP_MESSAGE_STEP = 0x6,
+ MFVP_MESSAGE_CANCELSTEP = 0x7
+} MFVP_MESSAGE_TYPE;
+
+DEFINE_GUID(IID_IMFVideoPresenter, 0x29AFF080, 0x182A, 0x4a5d, 0xAF,0x3B, 0x44,0x8F,0x3A,0x63,0x46,0xCB);
+MIDL_INTERFACE("29AFF080-182A-4a5d-AF3B-448F3A6346CB")
+IMFVideoPresenter : public IMFClockStateSink
+{
+public:
+ virtual HRESULT STDMETHODCALLTYPE ProcessMessage(MFVP_MESSAGE_TYPE eMessage, ULONG_PTR ulParam) = 0;
+ virtual HRESULT STDMETHODCALLTYPE GetCurrentMediaType(IMFVideoMediaType **ppMediaType) = 0;
+};
+#ifdef __CRT_UUID_DECL
+__CRT_UUID_DECL(IMFVideoPresenter, 0x29AFF080, 0x182A, 0x4a5d, 0xAF,0x3B, 0x44,0x8F,0x3A,0x63,0x46,0xCB)
+#endif
+#endif // __IMFVideoPresenter_INTERFACE_DEFINED__
+
+#ifndef __IMFRateSupport_INTERFACE_DEFINED__
+#define __IMFRateSupport_INTERFACE_DEFINED__
+DEFINE_GUID(IID_IMFRateSupport, 0x0a9ccdbc, 0xd797, 0x4563, 0x96,0x67, 0x94,0xec,0x5d,0x79,0x29,0x2d);
+MIDL_INTERFACE("0a9ccdbc-d797-4563-9667-94ec5d79292d")
+IMFRateSupport : public IUnknown
+{
+public:
+ virtual HRESULT STDMETHODCALLTYPE GetSlowestRate(MFRATE_DIRECTION eDirection, BOOL fThin, float *pflRate) = 0;
+ virtual HRESULT STDMETHODCALLTYPE GetFastestRate(MFRATE_DIRECTION eDirection, BOOL fThin, float *pflRate) = 0;
+ virtual HRESULT STDMETHODCALLTYPE IsRateSupported(BOOL fThin, float flRate, float *pflNearestSupportedRate) = 0;
+};
+#ifdef __CRT_UUID_DECL
+__CRT_UUID_DECL(IMFRateSupport, 0x0a9ccdbc, 0xd797, 0x4563, 0x96,0x67, 0x94,0xec,0x5d,0x79,0x29,0x2d)
+#endif
+#endif // __IMFRateSupport_INTERFACE_DEFINED__
+
+#ifndef __IMFTopologyServiceLookup_INTERFACE_DEFINED__
+#define __IMFTopologyServiceLookup_INTERFACE_DEFINED__
+typedef enum _MF_SERVICE_LOOKUP_TYPE
+{
+ MF_SERVICE_LOOKUP_UPSTREAM = 0,
+ MF_SERVICE_LOOKUP_UPSTREAM_DIRECT = (MF_SERVICE_LOOKUP_UPSTREAM + 1),
+ MF_SERVICE_LOOKUP_DOWNSTREAM = (MF_SERVICE_LOOKUP_UPSTREAM_DIRECT + 1),
+ MF_SERVICE_LOOKUP_DOWNSTREAM_DIRECT = (MF_SERVICE_LOOKUP_DOWNSTREAM + 1),
+ MF_SERVICE_LOOKUP_ALL = (MF_SERVICE_LOOKUP_DOWNSTREAM_DIRECT + 1),
+ MF_SERVICE_LOOKUP_GLOBAL = (MF_SERVICE_LOOKUP_ALL + 1)
+} MF_SERVICE_LOOKUP_TYPE;
+
+DEFINE_GUID(IID_IMFTopologyServiceLookup, 0xfa993889, 0x4383, 0x415a, 0xa9,0x30, 0xdd,0x47,0x2a,0x8c,0xf6,0xf7);
+MIDL_INTERFACE("fa993889-4383-415a-a930-dd472a8cf6f7")
+IMFTopologyServiceLookup : public IUnknown
+{
+public:
+ virtual HRESULT STDMETHODCALLTYPE LookupService(MF_SERVICE_LOOKUP_TYPE Type,
+ DWORD dwIndex,
+ REFGUID guidService,
+ REFIID riid,
+ LPVOID *ppvObjects,
+ DWORD *pnObjects) = 0;
+};
+#ifdef __CRT_UUID_DECL
+__CRT_UUID_DECL(IMFTopologyServiceLookup, 0xfa993889, 0x4383, 0x415a, 0xa9,0x30, 0xdd,0x47,0x2a,0x8c,0xf6,0xf7)
+#endif
+#endif // __IMFTopologyServiceLookup_INTERFACE_DEFINED__
+
+#ifndef __IMFTopologyServiceLookupClient_INTERFACE_DEFINED__
+#define __IMFTopologyServiceLookupClient_INTERFACE_DEFINED__
+DEFINE_GUID(IID_IMFTopologyServiceLookupClient, 0xfa99388a, 0x4383, 0x415a, 0xa9,0x30, 0xdd,0x47,0x2a,0x8c,0xf6,0xf7);
+MIDL_INTERFACE("fa99388a-4383-415a-a930-dd472a8cf6f7")
+IMFTopologyServiceLookupClient : public IUnknown
+{
+public:
+ virtual HRESULT STDMETHODCALLTYPE InitServicePointers(IMFTopologyServiceLookup *pLookup) = 0;
+ virtual HRESULT STDMETHODCALLTYPE ReleaseServicePointers(void) = 0;
+};
+#ifdef __CRT_UUID_DECL
+__CRT_UUID_DECL(IMFTopologyServiceLookupClient, 0xfa99388a, 0x4383, 0x415a, 0xa9,0x30, 0xdd,0x47,0x2a,0x8c,0xf6,0xf7)
+#endif
+#endif // __IMFTopologyServiceLookupClient_INTERFACE_DEFINED__
+
+#ifndef __IMediaEventSink_INTERFACE_DEFINED__
+#define __IMediaEventSink_INTERFACE_DEFINED__
+DEFINE_GUID(IID_IMediaEventSink, 0x56a868a2, 0x0ad4, 0x11ce, 0xb0,0x3a, 0x00,0x20,0xaf,0x0b,0xa7,0x70);
+MIDL_INTERFACE("56a868a2-0ad4-11ce-b03a-0020af0ba770")
+IMediaEventSink : public IUnknown
+{
+public:
+ virtual HRESULT STDMETHODCALLTYPE Notify(long EventCode, LONG_PTR EventParam1, LONG_PTR EventParam2) = 0;
+};
+#ifdef __CRT_UUID_DECL
+__CRT_UUID_DECL(IMediaEventSink, 0x56a868a2, 0x0ad4, 0x11ce, 0xb0,0x3a, 0x00,0x20,0xaf,0x0b,0xa7,0x70)
+#endif
+#endif // __IMediaEventSink_INTERFACE_DEFINED__
+
+#ifndef __IMFVideoRenderer_INTERFACE_DEFINED__
+#define __IMFVideoRenderer_INTERFACE_DEFINED__
+DEFINE_GUID(IID_IMFVideoRenderer, 0xDFDFD197, 0xA9CA, 0x43d8, 0xB3,0x41, 0x6A,0xF3,0x50,0x37,0x92,0xCD);
+MIDL_INTERFACE("DFDFD197-A9CA-43d8-B341-6AF3503792CD")
+IMFVideoRenderer : public IUnknown
+{
+public:
+ virtual HRESULT STDMETHODCALLTYPE InitializeRenderer(IMFTransform *pVideoMixer,
+ IMFVideoPresenter *pVideoPresenter) = 0;
+};
+#ifdef __CRT_UUID_DECL
+__CRT_UUID_DECL(IMFVideoRenderer, 0xDFDFD197, 0xA9CA, 0x43d8, 0xB3,0x41, 0x6A,0xF3,0x50,0x37,0x92,0xCD)
+#endif
+#endif // __IMFVideoRenderer_INTERFACE_DEFINED__
+
+#ifndef __IMFTrackedSample_INTERFACE_DEFINED__
+#define __IMFTrackedSample_INTERFACE_DEFINED__
+DEFINE_GUID(IID_IMFTrackedSample, 0x245BF8E9, 0x0755, 0x40f7, 0x88,0xA5, 0xAE,0x0F,0x18,0xD5,0x5E,0x17);
+MIDL_INTERFACE("245BF8E9-0755-40f7-88A5-AE0F18D55E17")
+IMFTrackedSample : public IUnknown
+{
+public:
+ virtual HRESULT STDMETHODCALLTYPE SetAllocator(IMFAsyncCallback *pSampleAllocator, IUnknown *pUnkState) = 0;
+};
+#ifdef __CRT_UUID_DECL
+__CRT_UUID_DECL(IMFTrackedSample, 0x245BF8E9, 0x0755, 0x40f7, 0x88,0xA5, 0xAE,0x0F,0x18,0xD5,0x5E,0x17)
+#endif
+#endif // __IMFTrackedSample_INTERFACE_DEFINED__
+
+#ifndef __IMFDesiredSample_INTERFACE_DEFINED__
+#define __IMFDesiredSample_INTERFACE_DEFINED__
+DEFINE_GUID(IID_IMFDesiredSample, 0x56C294D0, 0x753E, 0x4260, 0x8D,0x61, 0xA3,0xD8,0x82,0x0B,0x1D,0x54);
+MIDL_INTERFACE("56C294D0-753E-4260-8D61-A3D8820B1D54")
+IMFDesiredSample : public IUnknown
+{
+public:
+ virtual HRESULT STDMETHODCALLTYPE GetDesiredSampleTimeAndDuration(LONGLONG *phnsSampleTime,
+ LONGLONG *phnsSampleDuration) = 0;
+ virtual void STDMETHODCALLTYPE SetDesiredSampleTimeAndDuration(LONGLONG hnsSampleTime,
+ LONGLONG hnsSampleDuration) = 0;
+ virtual void STDMETHODCALLTYPE Clear( void) = 0;
+};
+#ifdef __CRT_UUID_DECL
+__CRT_UUID_DECL(IMFDesiredSample, 0x56C294D0, 0x753E, 0x4260, 0x8D,0x61, 0xA3,0xD8,0x82,0x0B,0x1D,0x54)
+#endif
+#endif
+
+#endif // EVRDEFS_H
+
diff --git a/src/multimedia/platform/wmf/evr/evrhelpers.cpp b/src/multimedia/platform/wmf/evr/evrhelpers.cpp
new file mode 100644
index 000000000..aa2311f46
--- /dev/null
+++ b/src/multimedia/platform/wmf/evr/evrhelpers.cpp
@@ -0,0 +1,186 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $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 "evrhelpers_p.h"
+
+#ifndef D3DFMT_YV12
+#define D3DFMT_YV12 (D3DFORMAT)MAKEFOURCC ('Y', 'V', '1', '2')
+#endif
+#ifndef D3DFMT_NV12
+#define D3DFMT_NV12 (D3DFORMAT)MAKEFOURCC ('N', 'V', '1', '2')
+#endif
+
+QT_BEGIN_NAMESPACE
+
+HRESULT qt_evr_getFourCC(IMFMediaType *type, DWORD *fourCC)
+{
+ if (!fourCC)
+ return E_POINTER;
+
+ HRESULT hr = S_OK;
+ GUID guidSubType = GUID_NULL;
+
+ if (SUCCEEDED(hr))
+ hr = type->GetGUID(MF_MT_SUBTYPE, &guidSubType);
+
+ if (SUCCEEDED(hr))
+ *fourCC = guidSubType.Data1;
+
+ return hr;
+}
+
+bool qt_evr_areMediaTypesEqual(IMFMediaType *type1, IMFMediaType *type2)
+{
+ if (!type1 && !type2)
+ return true;
+ if (!type1 || !type2)
+ return false;
+
+ DWORD dwFlags = 0;
+ HRESULT hr = type1->IsEqual(type2, &dwFlags);
+
+ return (hr == S_OK);
+}
+
+HRESULT qt_evr_validateVideoArea(const MFVideoArea& area, UINT32 width, UINT32 height)
+{
+ float fOffsetX = qt_evr_MFOffsetToFloat(area.OffsetX);
+ float fOffsetY = qt_evr_MFOffsetToFloat(area.OffsetY);
+
+ if ( ((LONG)fOffsetX + area.Area.cx > (LONG)width) ||
+ ((LONG)fOffsetY + area.Area.cy > (LONG)height) ) {
+ return MF_E_INVALIDMEDIATYPE;
+ }
+ return S_OK;
+}
+
+bool qt_evr_isSampleTimePassed(IMFClock *clock, IMFSample *sample)
+{
+ if (!sample || !clock)
+ return false;
+
+ HRESULT hr = S_OK;
+ MFTIME hnsTimeNow = 0;
+ MFTIME hnsSystemTime = 0;
+ MFTIME hnsSampleStart = 0;
+ MFTIME hnsSampleDuration = 0;
+
+ hr = clock->GetCorrelatedTime(0, &hnsTimeNow, &hnsSystemTime);
+
+ if (SUCCEEDED(hr))
+ hr = sample->GetSampleTime(&hnsSampleStart);
+
+ if (SUCCEEDED(hr))
+ hr = sample->GetSampleDuration(&hnsSampleDuration);
+
+ if (SUCCEEDED(hr)) {
+ if (hnsSampleStart + hnsSampleDuration < hnsTimeNow)
+ return true;
+ }
+
+ return false;
+}
+
+QVideoFrame::PixelFormat qt_evr_pixelFormatFromD3DFormat(DWORD format)
+{
+ switch (format) {
+ case D3DFMT_R8G8B8:
+ return QVideoFrame::Format_RGB24;
+ case D3DFMT_A8R8G8B8:
+ return QVideoFrame::Format_ARGB32;
+ case D3DFMT_X8R8G8B8:
+ return QVideoFrame::Format_RGB32;
+ case D3DFMT_R5G6B5:
+ return QVideoFrame::Format_RGB565;
+ case D3DFMT_X1R5G5B5:
+ return QVideoFrame::Format_RGB555;
+ case D3DFMT_A8:
+ return QVideoFrame::Format_Y8;
+ case D3DFMT_A8B8G8R8:
+ return QVideoFrame::Format_BGRA32;
+ case D3DFMT_X8B8G8R8:
+ return QVideoFrame::Format_BGR32;
+ case D3DFMT_UYVY:
+ return QVideoFrame::Format_UYVY;
+ case D3DFMT_YUY2:
+ return QVideoFrame::Format_YUYV;
+ case D3DFMT_NV12:
+ return QVideoFrame::Format_NV12;
+ case D3DFMT_YV12:
+ return QVideoFrame::Format_YV12;
+ case D3DFMT_UNKNOWN:
+ default:
+ return QVideoFrame::Format_Invalid;
+ }
+}
+
+D3DFORMAT qt_evr_D3DFormatFromPixelFormat(QVideoFrame::PixelFormat format)
+{
+ switch (format) {
+ case QVideoFrame::Format_RGB24:
+ return D3DFMT_R8G8B8;
+ case QVideoFrame::Format_ARGB32:
+ return D3DFMT_A8R8G8B8;
+ case QVideoFrame::Format_RGB32:
+ return D3DFMT_X8R8G8B8;
+ case QVideoFrame::Format_RGB565:
+ return D3DFMT_R5G6B5;
+ case QVideoFrame::Format_RGB555:
+ return D3DFMT_X1R5G5B5;
+ case QVideoFrame::Format_Y8:
+ return D3DFMT_A8;
+ case QVideoFrame::Format_BGRA32:
+ return D3DFMT_A8B8G8R8;
+ case QVideoFrame::Format_BGR32:
+ return D3DFMT_X8B8G8R8;
+ case QVideoFrame::Format_UYVY:
+ return D3DFMT_UYVY;
+ case QVideoFrame::Format_YUYV:
+ return D3DFMT_YUY2;
+ case QVideoFrame::Format_NV12:
+ return D3DFMT_NV12;
+ case QVideoFrame::Format_YV12:
+ return D3DFMT_YV12;
+ case QVideoFrame::Format_Invalid:
+ default:
+ return D3DFMT_UNKNOWN;
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/multimedia/platform/wmf/evr/evrhelpers_p.h b/src/multimedia/platform/wmf/evr/evrhelpers_p.h
new file mode 100644
index 000000000..89bff6288
--- /dev/null
+++ b/src/multimedia/platform/wmf/evr/evrhelpers_p.h
@@ -0,0 +1,112 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $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$
+**
+****************************************************************************/
+
+#ifndef EVRHELPERS_H
+#define EVRHELPERS_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "evrdefs_p.h"
+#include <qvideoframe.h>
+
+QT_BEGIN_NAMESPACE
+
+template<class T>
+static inline void qt_evr_safe_release(T **unk)
+{
+ if (*unk) {
+ (*unk)->Release();
+ *unk = NULL;
+ }
+}
+
+HRESULT qt_evr_getFourCC(IMFMediaType *type, DWORD *fourCC);
+
+bool qt_evr_areMediaTypesEqual(IMFMediaType *type1, IMFMediaType *type2);
+
+HRESULT qt_evr_validateVideoArea(const MFVideoArea& area, UINT32 width, UINT32 height);
+
+bool qt_evr_isSampleTimePassed(IMFClock *clock, IMFSample *sample);
+
+inline float qt_evr_MFOffsetToFloat(const MFOffset& offset)
+{
+ return offset.value + (float(offset.fract) / 65536);
+}
+
+inline MFOffset qt_evr_makeMFOffset(float v)
+{
+ MFOffset offset;
+ offset.value = short(v);
+ offset.fract = WORD(65536 * (v-offset.value));
+ return offset;
+}
+
+inline MFVideoArea qt_evr_makeMFArea(float x, float y, DWORD width, DWORD height)
+{
+ MFVideoArea area;
+ area.OffsetX = qt_evr_makeMFOffset(x);
+ area.OffsetY = qt_evr_makeMFOffset(y);
+ area.Area.cx = width;
+ area.Area.cy = height;
+ return area;
+}
+
+inline HRESULT qt_evr_getFrameRate(IMFMediaType *pType, MFRatio *pRatio)
+{
+ return MFGetAttributeRatio(pType, MF_MT_FRAME_RATE,
+ reinterpret_cast<UINT32*>(&pRatio->Numerator),
+ reinterpret_cast<UINT32*>(&pRatio->Denominator));
+}
+
+QVideoFrame::PixelFormat qt_evr_pixelFormatFromD3DFormat(DWORD format);
+D3DFORMAT qt_evr_D3DFormatFromPixelFormat(QVideoFrame::PixelFormat format);
+
+QT_END_NAMESPACE
+
+#endif // EVRHELPERS_H
+
diff --git a/src/multimedia/platform/wmf/evr/evrvideowindowcontrol.cpp b/src/multimedia/platform/wmf/evr/evrvideowindowcontrol.cpp
new file mode 100644
index 000000000..523dddca8
--- /dev/null
+++ b/src/multimedia/platform/wmf/evr/evrvideowindowcontrol.cpp
@@ -0,0 +1,350 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $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 "evrvideowindowcontrol_p.h"
+
+EvrVideoWindowControl::EvrVideoWindowControl(QObject *parent)
+ : QVideoWindowControl(parent)
+ , m_windowId(0)
+ , m_windowColor(RGB(0, 0, 0))
+ , m_dirtyValues(0)
+ , m_aspectRatioMode(Qt::KeepAspectRatio)
+ , m_brightness(0)
+ , m_contrast(0)
+ , m_hue(0)
+ , m_saturation(0)
+ , m_fullScreen(false)
+ , m_displayControl(0)
+ , m_processor(0)
+{
+}
+
+EvrVideoWindowControl::~EvrVideoWindowControl()
+{
+ clear();
+}
+
+bool EvrVideoWindowControl::setEvr(IUnknown *evr)
+{
+ clear();
+
+ if (!evr)
+ return true;
+
+ IMFGetService *service = NULL;
+
+ if (SUCCEEDED(evr->QueryInterface(IID_PPV_ARGS(&service)))
+ && SUCCEEDED(service->GetService(mr_VIDEO_RENDER_SERVICE, IID_PPV_ARGS(&m_displayControl)))) {
+
+ service->GetService(mr_VIDEO_MIXER_SERVICE, IID_PPV_ARGS(&m_processor));
+
+ setWinId(m_windowId);
+ setDisplayRect(m_displayRect);
+ setAspectRatioMode(m_aspectRatioMode);
+ m_dirtyValues = DXVA2_ProcAmp_Brightness | DXVA2_ProcAmp_Contrast | DXVA2_ProcAmp_Hue | DXVA2_ProcAmp_Saturation;
+ applyImageControls();
+ }
+
+ if (service)
+ service->Release();
+
+ return m_displayControl != NULL;
+}
+
+void EvrVideoWindowControl::clear()
+{
+ if (m_displayControl)
+ m_displayControl->Release();
+ m_displayControl = NULL;
+
+ if (m_processor)
+ m_processor->Release();
+ m_processor = NULL;
+}
+
+WId EvrVideoWindowControl::winId() const
+{
+ return m_windowId;
+}
+
+void EvrVideoWindowControl::setWinId(WId id)
+{
+ m_windowId = id;
+
+ if (m_displayControl)
+ m_displayControl->SetVideoWindow(HWND(m_windowId));
+}
+
+QRect EvrVideoWindowControl::displayRect() const
+{
+ return m_displayRect;
+}
+
+void EvrVideoWindowControl::setDisplayRect(const QRect &rect)
+{
+ m_displayRect = rect;
+
+ if (m_displayControl) {
+ RECT displayRect = { rect.left(), rect.top(), rect.right() + 1, rect.bottom() + 1 };
+ QSize sourceSize = nativeSize();
+
+ RECT sourceRect = { 0, 0, sourceSize.width(), sourceSize.height() };
+
+ if (m_aspectRatioMode == Qt::KeepAspectRatioByExpanding) {
+ QSize clippedSize = rect.size();
+ clippedSize.scale(sourceRect.right, sourceRect.bottom, Qt::KeepAspectRatio);
+
+ sourceRect.left = (sourceRect.right - clippedSize.width()) / 2;
+ sourceRect.top = (sourceRect.bottom - clippedSize.height()) / 2;
+ sourceRect.right = sourceRect.left + clippedSize.width();
+ sourceRect.bottom = sourceRect.top + clippedSize.height();
+ }
+
+ if (sourceSize.width() > 0 && sourceSize.height() > 0) {
+ MFVideoNormalizedRect sourceNormRect;
+ sourceNormRect.left = float(sourceRect.left) / float(sourceRect.right);
+ sourceNormRect.top = float(sourceRect.top) / float(sourceRect.bottom);
+ sourceNormRect.right = float(sourceRect.right) / float(sourceRect.right);
+ sourceNormRect.bottom = float(sourceRect.bottom) / float(sourceRect.bottom);
+ m_displayControl->SetVideoPosition(&sourceNormRect, &displayRect);
+ } else {
+ m_displayControl->SetVideoPosition(NULL, &displayRect);
+ }
+
+ // To refresh content immediately.
+ repaint();
+ }
+}
+
+bool EvrVideoWindowControl::isFullScreen() const
+{
+ return m_fullScreen;
+}
+
+void EvrVideoWindowControl::setFullScreen(bool fullScreen)
+{
+ if (m_fullScreen == fullScreen)
+ return;
+ emit fullScreenChanged(m_fullScreen = fullScreen);
+}
+
+void EvrVideoWindowControl::repaint()
+{
+ QSize size = nativeSize();
+ if (size.width() > 0 && size.height() > 0
+ && m_displayControl
+ && SUCCEEDED(m_displayControl->RepaintVideo())) {
+ return;
+ }
+
+ PAINTSTRUCT paint;
+ if (HDC dc = ::BeginPaint(HWND(m_windowId), &paint)) {
+ HPEN pen = ::CreatePen(PS_SOLID, 1, m_windowColor);
+ HBRUSH brush = ::CreateSolidBrush(m_windowColor);
+ ::SelectObject(dc, pen);
+ ::SelectObject(dc, brush);
+
+ ::Rectangle(
+ dc,
+ m_displayRect.left(),
+ m_displayRect.top(),
+ m_displayRect.right() + 1,
+ m_displayRect.bottom() + 1);
+
+ ::DeleteObject(pen);
+ ::DeleteObject(brush);
+ ::EndPaint(HWND(m_windowId), &paint);
+ }
+}
+
+QSize EvrVideoWindowControl::nativeSize() const
+{
+ QSize size;
+ if (m_displayControl) {
+ SIZE sourceSize;
+ if (SUCCEEDED(m_displayControl->GetNativeVideoSize(&sourceSize, 0)))
+ size = QSize(sourceSize.cx, sourceSize.cy);
+ }
+ return size;
+}
+
+Qt::AspectRatioMode EvrVideoWindowControl::aspectRatioMode() const
+{
+ return m_aspectRatioMode;
+}
+
+void EvrVideoWindowControl::setAspectRatioMode(Qt::AspectRatioMode mode)
+{
+ m_aspectRatioMode = mode;
+
+ if (m_displayControl) {
+ switch (mode) {
+ case Qt::IgnoreAspectRatio:
+ //comment from MSDN: Do not maintain the aspect ratio of the video. Stretch the video to fit the output rectangle.
+ m_displayControl->SetAspectRatioMode(MFVideoARMode_None);
+ break;
+ case Qt::KeepAspectRatio:
+ //comment from MSDN: Preserve the aspect ratio of the video by letterboxing or within the output rectangle.
+ m_displayControl->SetAspectRatioMode(MFVideoARMode_PreservePicture);
+ break;
+ case Qt::KeepAspectRatioByExpanding:
+ //for this mode, more adjustment will be done in setDisplayRect
+ m_displayControl->SetAspectRatioMode(MFVideoARMode_PreservePicture);
+ break;
+ default:
+ break;
+ }
+ setDisplayRect(m_displayRect);
+ }
+}
+
+int EvrVideoWindowControl::brightness() const
+{
+ return m_brightness;
+}
+
+void EvrVideoWindowControl::setBrightness(int brightness)
+{
+ if (m_brightness == brightness)
+ return;
+
+ m_brightness = brightness;
+
+ m_dirtyValues |= DXVA2_ProcAmp_Brightness;
+
+ applyImageControls();
+
+ emit brightnessChanged(brightness);
+}
+
+int EvrVideoWindowControl::contrast() const
+{
+ return m_contrast;
+}
+
+void EvrVideoWindowControl::setContrast(int contrast)
+{
+ if (m_contrast == contrast)
+ return;
+
+ m_contrast = contrast;
+
+ m_dirtyValues |= DXVA2_ProcAmp_Contrast;
+
+ applyImageControls();
+
+ emit contrastChanged(contrast);
+}
+
+int EvrVideoWindowControl::hue() const
+{
+ return m_hue;
+}
+
+void EvrVideoWindowControl::setHue(int hue)
+{
+ if (m_hue == hue)
+ return;
+
+ m_hue = hue;
+
+ m_dirtyValues |= DXVA2_ProcAmp_Hue;
+
+ applyImageControls();
+
+ emit hueChanged(hue);
+}
+
+int EvrVideoWindowControl::saturation() const
+{
+ return m_saturation;
+}
+
+void EvrVideoWindowControl::setSaturation(int saturation)
+{
+ if (m_saturation == saturation)
+ return;
+
+ m_saturation = saturation;
+
+ m_dirtyValues |= DXVA2_ProcAmp_Saturation;
+
+ applyImageControls();
+
+ emit saturationChanged(saturation);
+}
+
+void EvrVideoWindowControl::applyImageControls()
+{
+ if (m_processor) {
+ DXVA2_ProcAmpValues values;
+ if (m_dirtyValues & DXVA2_ProcAmp_Brightness) {
+ values.Brightness = scaleProcAmpValue(DXVA2_ProcAmp_Brightness, m_brightness);
+ }
+ if (m_dirtyValues & DXVA2_ProcAmp_Contrast) {
+ values.Contrast = scaleProcAmpValue(DXVA2_ProcAmp_Contrast, m_contrast);
+ }
+ if (m_dirtyValues & DXVA2_ProcAmp_Hue) {
+ values.Hue = scaleProcAmpValue(DXVA2_ProcAmp_Hue, m_hue);
+ }
+ if (m_dirtyValues & DXVA2_ProcAmp_Saturation) {
+ values.Saturation = scaleProcAmpValue(DXVA2_ProcAmp_Saturation, m_saturation);
+ }
+
+ if (SUCCEEDED(m_processor->SetProcAmpValues(m_dirtyValues, &values))) {
+ m_dirtyValues = 0;
+ }
+ }
+}
+
+DXVA2_Fixed32 EvrVideoWindowControl::scaleProcAmpValue(DWORD prop, int value) const
+{
+ float scaledValue = 0.0;
+
+ DXVA2_ValueRange range;
+ if (SUCCEEDED(m_processor->GetProcAmpRange(prop, &range))) {
+ scaledValue = DXVA2FixedToFloat(range.DefaultValue);
+ if (value > 0)
+ scaledValue += float(value) * (DXVA2FixedToFloat(range.MaxValue) - DXVA2FixedToFloat(range.DefaultValue)) / 100;
+ else if (value < 0)
+ scaledValue -= float(value) * (DXVA2FixedToFloat(range.MinValue) - DXVA2FixedToFloat(range.DefaultValue)) / 100;
+ }
+
+ return DXVA2FloatToFixed(scaledValue);
+}
diff --git a/src/multimedia/platform/wmf/evr/evrvideowindowcontrol_p.h b/src/multimedia/platform/wmf/evr/evrvideowindowcontrol_p.h
new file mode 100644
index 000000000..059376f7e
--- /dev/null
+++ b/src/multimedia/platform/wmf/evr/evrvideowindowcontrol_p.h
@@ -0,0 +1,120 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef EVRVIDEOWINDOWCONTROL_H
+#define EVRVIDEOWINDOWCONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qvideowindowcontrol.h"
+
+#include "evrdefs_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class EvrVideoWindowControl : public QVideoWindowControl
+{
+ Q_OBJECT
+public:
+ EvrVideoWindowControl(QObject *parent = 0);
+ ~EvrVideoWindowControl() override;
+
+ bool setEvr(IUnknown *evr);
+
+ WId winId() const override;
+ void setWinId(WId id) override;
+
+ QRect displayRect() const override;
+ void setDisplayRect(const QRect &rect) override;
+
+ bool isFullScreen() const override;
+ void setFullScreen(bool fullScreen) override;
+
+ void repaint() override;
+
+ QSize nativeSize() const override;
+
+ Qt::AspectRatioMode aspectRatioMode() const override;
+ void setAspectRatioMode(Qt::AspectRatioMode mode) override;
+
+ int brightness() const override;
+ void setBrightness(int brightness) override;
+
+ int contrast() const override;
+ void setContrast(int contrast) override;
+
+ int hue() const override;
+ void setHue(int hue) override;
+
+ int saturation() const override;
+ void setSaturation(int saturation) override;
+
+ void applyImageControls();
+
+private:
+ void clear();
+ DXVA2_Fixed32 scaleProcAmpValue(DWORD prop, int value) const;
+
+ WId m_windowId;
+ COLORREF m_windowColor;
+ DWORD m_dirtyValues;
+ Qt::AspectRatioMode m_aspectRatioMode;
+ QRect m_displayRect;
+ int m_brightness;
+ int m_contrast;
+ int m_hue;
+ int m_saturation;
+ bool m_fullScreen;
+
+ IMFVideoDisplayControl *m_displayControl;
+ IMFVideoProcessor *m_processor;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/platform/wmf/mfstream.cpp b/src/multimedia/platform/wmf/mfstream.cpp
new file mode 100644
index 000000000..b01dbb7b1
--- /dev/null
+++ b/src/multimedia/platform/wmf/mfstream.cpp
@@ -0,0 +1,361 @@
+/****************************************************************************
+**
+** 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 "mfstream_p.h"
+#include <QtCore/qcoreapplication.h>
+
+//MFStream is added for supporting QIODevice type of media source.
+//It is used to delegate invocations from media foundation(through IMFByteStream) to QIODevice.
+
+MFStream::MFStream(QIODevice *stream, bool ownStream)
+ : m_cRef(1)
+ , m_stream(stream)
+ , m_ownStream(ownStream)
+ , m_currentReadResult(0)
+{
+ //Move to the thread of the stream object
+ //to make sure invocations on stream
+ //are happened in the same thread of stream object
+ this->moveToThread(stream->thread());
+ connect(stream, SIGNAL(readyRead()), this, SLOT(handleReadyRead()));
+}
+
+MFStream::~MFStream()
+{
+ if (m_currentReadResult)
+ m_currentReadResult->Release();
+ if (m_ownStream)
+ m_stream->deleteLater();
+}
+
+//from IUnknown
+STDMETHODIMP MFStream::QueryInterface(REFIID riid, LPVOID *ppvObject)
+{
+ if (!ppvObject)
+ return E_POINTER;
+ if (riid == IID_IMFByteStream) {
+ *ppvObject = static_cast<IMFByteStream*>(this);
+ } else if (riid == IID_IUnknown) {
+ *ppvObject = static_cast<IUnknown*>(this);
+ } else {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+ AddRef();
+ return S_OK;
+}
+
+STDMETHODIMP_(ULONG) MFStream::AddRef(void)
+{
+ return InterlockedIncrement(&m_cRef);
+}
+
+STDMETHODIMP_(ULONG) MFStream::Release(void)
+{
+ LONG cRef = InterlockedDecrement(&m_cRef);
+ if (cRef == 0) {
+ this->deleteLater();
+ }
+ return cRef;
+}
+
+
+//from IMFByteStream
+STDMETHODIMP MFStream::GetCapabilities(DWORD *pdwCapabilities)
+{
+ if (!pdwCapabilities)
+ return E_INVALIDARG;
+ *pdwCapabilities = MFBYTESTREAM_IS_READABLE;
+ if (!m_stream->isSequential())
+ *pdwCapabilities |= MFBYTESTREAM_IS_SEEKABLE;
+ return S_OK;
+}
+
+STDMETHODIMP MFStream::GetLength(QWORD *pqwLength)
+{
+ if (!pqwLength)
+ return E_INVALIDARG;
+ QMutexLocker locker(&m_mutex);
+ *pqwLength = QWORD(m_stream->size());
+ return S_OK;
+}
+
+STDMETHODIMP MFStream::SetLength(QWORD)
+{
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP MFStream::GetCurrentPosition(QWORD *pqwPosition)
+{
+ if (!pqwPosition)
+ return E_INVALIDARG;
+ QMutexLocker locker(&m_mutex);
+ *pqwPosition = m_stream->pos();
+ return S_OK;
+}
+
+STDMETHODIMP MFStream::SetCurrentPosition(QWORD qwPosition)
+{
+ QMutexLocker locker(&m_mutex);
+ //SetCurrentPosition may happend during the BeginRead/EndRead pair,
+ //refusing to execute SetCurrentPosition during that time seems to be
+ //the simplest workable solution
+ if (m_currentReadResult)
+ return S_FALSE;
+
+ bool seekOK = m_stream->seek(qint64(qwPosition));
+ if (seekOK)
+ return S_OK;
+ else
+ return S_FALSE;
+}
+
+STDMETHODIMP MFStream::IsEndOfStream(BOOL *pfEndOfStream)
+{
+ if (!pfEndOfStream)
+ return E_INVALIDARG;
+ QMutexLocker locker(&m_mutex);
+ *pfEndOfStream = m_stream->atEnd() ? TRUE : FALSE;
+ return S_OK;
+}
+
+STDMETHODIMP MFStream::Read(BYTE *pb, ULONG cb, ULONG *pcbRead)
+{
+ QMutexLocker locker(&m_mutex);
+ qint64 read = m_stream->read((char*)(pb), qint64(cb));
+ if (pcbRead)
+ *pcbRead = ULONG(read);
+ return S_OK;
+}
+
+STDMETHODIMP MFStream::BeginRead(BYTE *pb, ULONG cb, IMFAsyncCallback *pCallback,
+ IUnknown *punkState)
+{
+ if (!pCallback || !pb)
+ return E_INVALIDARG;
+
+ Q_ASSERT(m_currentReadResult == NULL);
+
+ AsyncReadState *state = new (std::nothrow) AsyncReadState(pb, cb);
+ if (state == NULL)
+ return E_OUTOFMEMORY;
+
+ HRESULT hr = MFCreateAsyncResult(state, pCallback, punkState, &m_currentReadResult);
+ state->Release();
+ if (FAILED(hr))
+ return hr;
+
+ QCoreApplication::postEvent(this, new QEvent(QEvent::User));
+ return hr;
+}
+
+STDMETHODIMP MFStream::EndRead(IMFAsyncResult* pResult, ULONG *pcbRead)
+{
+ if (!pcbRead)
+ return E_INVALIDARG;
+ IUnknown *pUnk;
+ pResult->GetObject(&pUnk);
+ AsyncReadState *state = static_cast<AsyncReadState*>(pUnk);
+ *pcbRead = state->bytesRead();
+ pUnk->Release();
+
+ m_currentReadResult->Release();
+ m_currentReadResult = NULL;
+
+ return S_OK;
+}
+
+STDMETHODIMP MFStream::Write(const BYTE *, ULONG, ULONG *)
+{
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP MFStream::BeginWrite(const BYTE *, ULONG ,
+ IMFAsyncCallback *,
+ IUnknown *)
+{
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP MFStream::EndWrite(IMFAsyncResult *,
+ ULONG *)
+{
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP MFStream::Seek(
+ MFBYTESTREAM_SEEK_ORIGIN SeekOrigin,
+ LONGLONG llSeekOffset,
+ DWORD,
+ QWORD *pqwCurrentPosition)
+{
+ QMutexLocker locker(&m_mutex);
+ if (m_currentReadResult)
+ return S_FALSE;
+
+ qint64 pos = qint64(llSeekOffset);
+ switch (SeekOrigin) {
+ case msoBegin:
+ break;
+ case msoCurrent:
+ pos += m_stream->pos();
+ break;
+ }
+ bool seekOK = m_stream->seek(pos);
+ if (pqwCurrentPosition)
+ *pqwCurrentPosition = pos;
+ if (seekOK)
+ return S_OK;
+ else
+ return S_FALSE;
+}
+
+STDMETHODIMP MFStream::Flush()
+{
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP MFStream::Close()
+{
+ QMutexLocker locker(&m_mutex);
+ if (m_ownStream)
+ m_stream->close();
+ return S_OK;
+}
+
+void MFStream::doRead()
+{
+ bool readDone = true;
+ IUnknown *pUnk = NULL;
+ HRESULT hr = m_currentReadResult->GetObject(&pUnk);
+ if (SUCCEEDED(hr)) {
+ //do actual read
+ AsyncReadState *state = static_cast<AsyncReadState*>(pUnk);
+ ULONG cbRead;
+ Read(state->pb(), state->cb() - state->bytesRead(), &cbRead);
+ pUnk->Release();
+
+ state->setBytesRead(cbRead + state->bytesRead());
+ if (state->cb() > state->bytesRead() && !m_stream->atEnd()) {
+ readDone = false;
+ }
+ }
+
+ if (readDone) {
+ //now inform the original caller
+ m_currentReadResult->SetStatus(hr);
+ MFInvokeCallback(m_currentReadResult);
+ }
+}
+
+
+void MFStream::handleReadyRead()
+{
+ doRead();
+}
+
+void MFStream::customEvent(QEvent *event)
+{
+ if (event->type() != QEvent::User) {
+ QObject::customEvent(event);
+ return;
+ }
+ doRead();
+}
+
+//AsyncReadState is a helper class used in BeginRead for asynchronous operation
+//to record some BeginRead parameters, so these parameters could be
+//used later when actually executing the read operation in another thread.
+MFStream::AsyncReadState::AsyncReadState(BYTE *pb, ULONG cb)
+ : m_cRef(1)
+ , m_pb(pb)
+ , m_cb(cb)
+ , m_cbRead(0)
+{
+}
+
+//from IUnknown
+STDMETHODIMP MFStream::AsyncReadState::QueryInterface(REFIID riid, LPVOID *ppvObject)
+{
+ if (!ppvObject)
+ return E_POINTER;
+
+ if (riid == IID_IUnknown) {
+ *ppvObject = static_cast<IUnknown*>(this);
+ } else {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+ AddRef();
+ return S_OK;
+}
+
+STDMETHODIMP_(ULONG) MFStream::AsyncReadState::AddRef(void)
+{
+ return InterlockedIncrement(&m_cRef);
+}
+
+STDMETHODIMP_(ULONG) MFStream::AsyncReadState::Release(void)
+{
+ LONG cRef = InterlockedDecrement(&m_cRef);
+ if (cRef == 0)
+ delete this;
+ // For thread safety, return a temporary variable.
+ return cRef;
+}
+
+BYTE* MFStream::AsyncReadState::pb() const
+{
+ return m_pb;
+}
+
+ULONG MFStream::AsyncReadState::cb() const
+{
+ return m_cb;
+}
+
+ULONG MFStream::AsyncReadState::bytesRead() const
+{
+ return m_cbRead;
+}
+
+void MFStream::AsyncReadState::setBytesRead(ULONG cbRead)
+{
+ m_cbRead = cbRead;
+}
diff --git a/src/multimedia/platform/wmf/mfstream_p.h b/src/multimedia/platform/wmf/mfstream_p.h
new file mode 100644
index 000000000..975d02c9d
--- /dev/null
+++ b/src/multimedia/platform/wmf/mfstream_p.h
@@ -0,0 +1,159 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef MFSTREAM_H
+#define MFSTREAM_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <mfapi.h>
+#include <mfidl.h>
+#include <QtCore/qmutex.h>
+#include <QtCore/qiodevice.h>
+#include <QtCore/qcoreevent.h>
+
+QT_USE_NAMESPACE
+
+class MFStream : public QObject, public IMFByteStream
+{
+ Q_OBJECT
+public:
+ MFStream(QIODevice *stream, bool ownStream);
+
+ ~MFStream();
+
+ //from IUnknown
+ STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject);
+
+ STDMETHODIMP_(ULONG) AddRef(void);
+
+ STDMETHODIMP_(ULONG) Release(void);
+
+
+ //from IMFByteStream
+ STDMETHODIMP GetCapabilities(DWORD *pdwCapabilities);
+
+ STDMETHODIMP GetLength(QWORD *pqwLength);
+
+ STDMETHODIMP SetLength(QWORD);
+
+ STDMETHODIMP GetCurrentPosition(QWORD *pqwPosition);
+
+ STDMETHODIMP SetCurrentPosition(QWORD qwPosition);
+
+ STDMETHODIMP IsEndOfStream(BOOL *pfEndOfStream);
+
+ STDMETHODIMP Read(BYTE *pb, ULONG cb, ULONG *pcbRead);
+
+ STDMETHODIMP BeginRead(BYTE *pb, ULONG cb, IMFAsyncCallback *pCallback,
+ IUnknown *punkState);
+
+ STDMETHODIMP EndRead(IMFAsyncResult* pResult, ULONG *pcbRead);
+
+ STDMETHODIMP Write(const BYTE *, ULONG, ULONG *);
+
+ STDMETHODIMP BeginWrite(const BYTE *, ULONG ,
+ IMFAsyncCallback *,
+ IUnknown *);
+
+ STDMETHODIMP EndWrite(IMFAsyncResult *,
+ ULONG *);
+
+ STDMETHODIMP Seek(
+ MFBYTESTREAM_SEEK_ORIGIN SeekOrigin,
+ LONGLONG llSeekOffset,
+ DWORD,
+ QWORD *pqwCurrentPosition);
+
+ STDMETHODIMP Flush();
+
+ STDMETHODIMP Close();
+
+private:
+ class AsyncReadState : public IUnknown
+ {
+ public:
+ AsyncReadState(BYTE *pb, ULONG cb);
+
+ //from IUnknown
+ STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject);
+
+ STDMETHODIMP_(ULONG) AddRef(void);
+
+ STDMETHODIMP_(ULONG) Release(void);
+
+ BYTE* pb() const;
+ ULONG cb() const;
+ ULONG bytesRead() const;
+
+ void setBytesRead(ULONG cbRead);
+
+ private:
+ long m_cRef;
+ BYTE *m_pb;
+ ULONG m_cb;
+ ULONG m_cbRead;
+ };
+
+ long m_cRef;
+ QIODevice *m_stream;
+ bool m_ownStream;
+ DWORD m_workQueueId;
+ QMutex m_mutex;
+
+ void doRead();
+
+private Q_SLOTS:
+ void handleReadyRead();
+
+protected:
+ void customEvent(QEvent *event);
+ IMFAsyncResult *m_currentReadResult;
+};
+
+#endif
diff --git a/src/multimedia/platform/wmf/player/mfactivate.cpp b/src/multimedia/platform/wmf/player/mfactivate.cpp
new file mode 100644
index 000000000..05d9321be
--- /dev/null
+++ b/src/multimedia/platform/wmf/player/mfactivate.cpp
@@ -0,0 +1,87 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $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 "mfactivate_p.h"
+
+#include <mfapi.h>
+
+MFAbstractActivate::MFAbstractActivate()
+ : m_attributes(0)
+ , m_cRef(1)
+{
+ MFCreateAttributes(&m_attributes, 0);
+}
+
+MFAbstractActivate::~MFAbstractActivate()
+{
+ if (m_attributes)
+ m_attributes->Release();
+}
+
+
+HRESULT MFAbstractActivate::QueryInterface(REFIID riid, LPVOID *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;
+}
+
+ULONG MFAbstractActivate::AddRef(void)
+{
+ return InterlockedIncrement(&m_cRef);
+}
+
+ULONG MFAbstractActivate::Release(void)
+{
+ ULONG cRef = InterlockedDecrement(&m_cRef);
+ if (cRef == 0)
+ delete this;
+ return cRef;
+}
diff --git a/src/multimedia/platform/wmf/player/mfactivate_p.h b/src/multimedia/platform/wmf/player/mfactivate_p.h
new file mode 100644
index 000000000..86ef1c438
--- /dev/null
+++ b/src/multimedia/platform/wmf/player/mfactivate_p.h
@@ -0,0 +1,223 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $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$
+**
+****************************************************************************/
+
+#ifndef MFACTIVATE_H
+#define MFACTIVATE_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <mfidl.h>
+
+class MFAbstractActivate : public IMFActivate
+{
+public:
+ explicit MFAbstractActivate();
+ virtual ~MFAbstractActivate();
+
+ //from IUnknown
+ STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject);
+ STDMETHODIMP_(ULONG) AddRef(void);
+ STDMETHODIMP_(ULONG) Release(void);
+
+ //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()
+ {
+ 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()
+ {
+ return m_attributes->LockStore();
+ }
+
+ STDMETHODIMP UnlockStore()
+ {
+ 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);
+ }
+
+private:
+ IMFAttributes *m_attributes;
+ ULONG m_cRef;
+};
+
+#endif // MFACTIVATE_H
diff --git a/src/multimedia/platform/wmf/player/mfaudioendpointcontrol.cpp b/src/multimedia/platform/wmf/player/mfaudioendpointcontrol.cpp
new file mode 100644
index 000000000..5e1f130cf
--- /dev/null
+++ b/src/multimedia/platform/wmf/player/mfaudioendpointcontrol.cpp
@@ -0,0 +1,180 @@
+/****************************************************************************
+**
+** 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 "QtCore/qdebug.h"
+#include "mfaudioendpointcontrol_p.h"
+
+#include <mmdeviceapi.h>
+
+MFAudioEndpointControl::MFAudioEndpointControl(QObject *parent)
+ : QAudioOutputSelectorControl(parent)
+ , m_currentActivate(0)
+{
+}
+
+MFAudioEndpointControl::~MFAudioEndpointControl()
+{
+ clear();
+}
+
+void MFAudioEndpointControl::clear()
+{
+ m_activeEndpoint.clear();
+
+ for (auto it = m_devices.cbegin(), end = m_devices.cend(); it != end; ++it)
+ CoTaskMemFree(it.value());
+
+ m_devices.clear();
+
+ if (m_currentActivate)
+ m_currentActivate->Release();
+ m_currentActivate = NULL;
+}
+
+QList<QString> MFAudioEndpointControl::availableOutputs() const
+{
+ return m_devices.keys();
+}
+
+QString MFAudioEndpointControl::outputDescription(const QString &name) const
+{
+ return name.section(QLatin1Char('\\'), -1);
+}
+
+QString MFAudioEndpointControl::defaultOutput() const
+{
+ return m_defaultEndpoint;
+}
+
+QString MFAudioEndpointControl::activeOutput() const
+{
+ return m_activeEndpoint;
+}
+
+void MFAudioEndpointControl::setActiveOutput(const QString &name)
+{
+ if (m_activeEndpoint == name)
+ return;
+ QMap<QString, LPWSTR>::iterator it = m_devices.find(name);
+ if (it == m_devices.end())
+ return;
+
+ LPWSTR wstrID = *it;
+ IMFActivate *activate = NULL;
+ HRESULT hr = MFCreateAudioRendererActivate(&activate);
+ if (FAILED(hr)) {
+ qWarning() << "Failed to create audio renderer activate";
+ return;
+ }
+
+ if (wstrID) {
+ hr = activate->SetString(MF_AUDIO_RENDERER_ATTRIBUTE_ENDPOINT_ID, wstrID);
+ } else {
+ //This is the default one that has been inserted in updateEndpoints(),
+ //so give the activate a hint that we want to use the device for multimedia playback
+ //then the media foundation will choose an appropriate one.
+
+ //from MSDN:
+ //The ERole enumeration defines constants that indicate the role that the system has assigned to an audio endpoint device.
+ //eMultimedia: Music, movies, narration, and live music recording.
+ hr = activate->SetUINT32(MF_AUDIO_RENDERER_ATTRIBUTE_ENDPOINT_ROLE, eMultimedia);
+ }
+
+ if (FAILED(hr)) {
+ qWarning() << "Failed to set attribute for audio device" << name;
+ return;
+ }
+
+ if (m_currentActivate)
+ m_currentActivate->Release();
+ m_currentActivate = activate;
+ m_activeEndpoint = name;
+}
+
+IMFActivate* MFAudioEndpointControl::createActivate()
+{
+ clear();
+
+ updateEndpoints();
+
+ // Check if an endpoint is available ("Default" is always inserted)
+ if (m_devices.count() <= 1)
+ return NULL;
+
+ setActiveOutput(m_defaultEndpoint);
+
+ return m_currentActivate;
+}
+
+void MFAudioEndpointControl::updateEndpoints()
+{
+ m_defaultEndpoint = QString::fromLatin1("Default");
+ m_devices.insert(m_defaultEndpoint, NULL);
+
+ IMMDeviceEnumerator *pEnum = NULL;
+ HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
+ NULL, CLSCTX_ALL,
+ __uuidof(IMMDeviceEnumerator),
+ (void**)&pEnum);
+ if (SUCCEEDED(hr)) {
+ IMMDeviceCollection *pDevices = NULL;
+ hr = pEnum->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &pDevices);
+ if (SUCCEEDED(hr)) {
+ UINT count;
+ hr = pDevices->GetCount(&count);
+ if (SUCCEEDED(hr)) {
+ for (UINT i = 0; i < count; ++i) {
+ IMMDevice *pDevice = NULL;
+ hr = pDevices->Item(i, &pDevice);
+ if (SUCCEEDED(hr)) {
+ LPWSTR wstrID = NULL;
+ hr = pDevice->GetId(&wstrID);
+ if (SUCCEEDED(hr)) {
+ QString deviceId = QString::fromWCharArray(wstrID);
+ m_devices.insert(deviceId, wstrID);
+ }
+ pDevice->Release();
+ }
+ }
+ }
+ pDevices->Release();
+ }
+ pEnum->Release();
+ }
+}
diff --git a/src/multimedia/platform/wmf/player/mfaudioendpointcontrol_p.h b/src/multimedia/platform/wmf/player/mfaudioendpointcontrol_p.h
new file mode 100644
index 000000000..21d404104
--- /dev/null
+++ b/src/multimedia/platform/wmf/player/mfaudioendpointcontrol_p.h
@@ -0,0 +1,93 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef MFAUDIOENDPOINTCONTROL_H
+#define MFAUDIOENDPOINTCONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <mfapi.h>
+#include <mfidl.h>
+
+#include "qaudiooutputselectorcontrol.h"
+
+class MFPlayerService;
+
+QT_USE_NAMESPACE
+
+class MFAudioEndpointControl : public QAudioOutputSelectorControl
+{
+ Q_OBJECT
+public:
+ MFAudioEndpointControl(QObject *parent = 0);
+ ~MFAudioEndpointControl();
+
+ QList<QString> availableOutputs() const;
+
+ QString outputDescription(const QString &name) const;
+
+ QString defaultOutput() const;
+ QString activeOutput() const;
+
+ void setActiveOutput(const QString& name);
+
+ IMFActivate* createActivate();
+
+private:
+ void clear();
+ void updateEndpoints();
+
+ QString m_defaultEndpoint;
+ QString m_activeEndpoint;
+ QMap<QString, LPWSTR> m_devices;
+ IMFActivate *m_currentActivate;
+
+};
+
+#endif
+
diff --git a/src/multimedia/platform/wmf/player/mfaudioprobecontrol.cpp b/src/multimedia/platform/wmf/player/mfaudioprobecontrol.cpp
new file mode 100644
index 000000000..a371629da
--- /dev/null
+++ b/src/multimedia/platform/wmf/player/mfaudioprobecontrol.cpp
@@ -0,0 +1,75 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $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 "mfaudioprobecontrol_p.h"
+
+MFAudioProbeControl::MFAudioProbeControl(QObject *parent):
+ QMediaAudioProbeControl(parent)
+{
+}
+
+MFAudioProbeControl::~MFAudioProbeControl()
+{
+}
+
+void MFAudioProbeControl::bufferProbed(const char *data, quint32 size, const QAudioFormat& format, qint64 startTime)
+{
+ if (!format.isValid())
+ return;
+
+ QAudioBuffer audioBuffer = QAudioBuffer(QByteArray(data, size), format, startTime);
+
+ {
+ QMutexLocker locker(&m_bufferMutex);
+ m_pendingBuffer = audioBuffer;
+ QMetaObject::invokeMethod(this, "bufferProbed", Qt::QueuedConnection);
+ }
+}
+
+void MFAudioProbeControl::bufferProbed()
+{
+ QAudioBuffer audioBuffer;
+ {
+ QMutexLocker locker(&m_bufferMutex);
+ if (!m_pendingBuffer.isValid())
+ return;
+ audioBuffer = m_pendingBuffer;
+ }
+ emit audioBufferProbed(audioBuffer);
+}
diff --git a/src/multimedia/platform/wmf/player/mfaudioprobecontrol_p.h b/src/multimedia/platform/wmf/player/mfaudioprobecontrol_p.h
new file mode 100644
index 000000000..0ccf151ad
--- /dev/null
+++ b/src/multimedia/platform/wmf/player/mfaudioprobecontrol_p.h
@@ -0,0 +1,77 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $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$
+**
+****************************************************************************/
+
+#ifndef MFAUDIOPROBECONTROL_H
+#define MFAUDIOPROBECONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <qmediaaudioprobecontrol.h>
+#include <QtCore/qmutex.h>
+#include <qaudiobuffer.h>
+
+QT_USE_NAMESPACE
+
+class MFAudioProbeControl : public QMediaAudioProbeControl
+{
+ Q_OBJECT
+public:
+ explicit MFAudioProbeControl(QObject *parent);
+ virtual ~MFAudioProbeControl();
+
+ void bufferProbed(const char *data, quint32 size, const QAudioFormat& format, qint64 startTime);
+
+private slots:
+ void bufferProbed();
+
+private:
+ QAudioBuffer m_pendingBuffer;
+ QMutex m_bufferMutex;
+};
+
+#endif
diff --git a/src/multimedia/platform/wmf/player/mfevrvideowindowcontrol.cpp b/src/multimedia/platform/wmf/player/mfevrvideowindowcontrol.cpp
new file mode 100644
index 000000000..24c176c24
--- /dev/null
+++ b/src/multimedia/platform/wmf/player/mfevrvideowindowcontrol.cpp
@@ -0,0 +1,91 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $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 "mfevrvideowindowcontrol_p.h"
+
+#include <qdebug.h>
+
+MFEvrVideoWindowControl::MFEvrVideoWindowControl(QObject *parent)
+ : EvrVideoWindowControl(parent)
+ , m_currentActivate(NULL)
+ , m_evrSink(NULL)
+{
+}
+
+MFEvrVideoWindowControl::~MFEvrVideoWindowControl()
+{
+ clear();
+}
+
+void MFEvrVideoWindowControl::clear()
+{
+ setEvr(NULL);
+
+ if (m_evrSink)
+ m_evrSink->Release();
+ if (m_currentActivate) {
+ m_currentActivate->ShutdownObject();
+ m_currentActivate->Release();
+ }
+ m_evrSink = NULL;
+ m_currentActivate = NULL;
+}
+
+IMFActivate* MFEvrVideoWindowControl::createActivate()
+{
+ clear();
+
+ if (FAILED(MFCreateVideoRendererActivate(0, &m_currentActivate))) {
+ qWarning() << "Failed to create evr video renderer activate!";
+ return NULL;
+ }
+ if (FAILED(m_currentActivate->ActivateObject(IID_IMFMediaSink, (LPVOID*)(&m_evrSink)))) {
+ qWarning() << "Failed to activate evr media sink!";
+ return NULL;
+ }
+ if (!setEvr(m_evrSink))
+ return NULL;
+
+ return m_currentActivate;
+}
+
+void MFEvrVideoWindowControl::releaseActivate()
+{
+ clear();
+}
diff --git a/src/multimedia/platform/wmf/player/mfevrvideowindowcontrol_p.h b/src/multimedia/platform/wmf/player/mfevrvideowindowcontrol_p.h
new file mode 100644
index 000000000..c74148431
--- /dev/null
+++ b/src/multimedia/platform/wmf/player/mfevrvideowindowcontrol_p.h
@@ -0,0 +1,74 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $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$
+**
+****************************************************************************/
+
+#ifndef MFEVRVIDEOWINDOWCONTROL_H
+#define MFEVRVIDEOWINDOWCONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "private/evrvideowindowcontrol_p.h"
+
+QT_USE_NAMESPACE
+
+class MFEvrVideoWindowControl : public EvrVideoWindowControl
+{
+public:
+ MFEvrVideoWindowControl(QObject *parent = 0);
+ ~MFEvrVideoWindowControl();
+
+ IMFActivate* createActivate();
+ void releaseActivate();
+
+private:
+ void clear();
+
+ IMFActivate *m_currentActivate;
+ IMFMediaSink *m_evrSink;
+};
+
+#endif // MFEVRVIDEOWINDOWCONTROL_H
diff --git a/src/multimedia/platform/wmf/player/mfmetadatacontrol.cpp b/src/multimedia/platform/wmf/player/mfmetadatacontrol.cpp
new file mode 100644
index 000000000..b7cf771e8
--- /dev/null
+++ b/src/multimedia/platform/wmf/player/mfmetadatacontrol.cpp
@@ -0,0 +1,431 @@
+/****************************************************************************
+**
+** 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 <qmediametadata.h>
+#include <qdatetime.h>
+#include <qimage.h>
+
+#include "mfmetadatacontrol_p.h"
+#include "mfplayerservice_p.h"
+#include "Propkey.h"
+
+//#define DEBUG_MEDIAFOUNDATION
+
+static QString nameForGUID(GUID guid)
+{
+ // Audio formats
+ if (guid == MFAudioFormat_AAC)
+ return QStringLiteral("MPEG AAC Audio");
+ else if (guid == MFAudioFormat_ADTS)
+ return QStringLiteral("MPEG ADTS AAC Audio");
+ else if (guid == MFAudioFormat_Dolby_AC3_SPDIF)
+ return QStringLiteral("Dolby AC-3 SPDIF");
+ else if (guid == MFAudioFormat_DRM)
+ return QStringLiteral("DRM");
+ else if (guid == MFAudioFormat_DTS)
+ return QStringLiteral("Digital Theater Systems Audio (DTS)");
+ else if (guid == MFAudioFormat_Float)
+ return QStringLiteral("IEEE Float Audio");
+ else if (guid == MFAudioFormat_MP3)
+ return QStringLiteral("MPEG Audio Layer-3 (MP3)");
+ else if (guid == MFAudioFormat_MPEG)
+ return QStringLiteral("MPEG-1 Audio");
+ else if (guid == MFAudioFormat_MSP1)
+ return QStringLiteral("Windows Media Audio Voice");
+ else if (guid == MFAudioFormat_PCM)
+ return QStringLiteral("Uncompressed PCM Audio");
+ else if (guid == MFAudioFormat_WMASPDIF)
+ return QStringLiteral("Windows Media Audio 9 SPDIF");
+ else if (guid == MFAudioFormat_WMAudioV8)
+ return QStringLiteral("Windows Media Audio 8 (WMA2)");
+ else if (guid == MFAudioFormat_WMAudioV9)
+ return QStringLiteral("Windows Media Audio 9 (WMA3");
+ else if (guid == MFAudioFormat_WMAudio_Lossless)
+ return QStringLiteral("Windows Media Audio 9 Lossless");
+
+ // Video formats
+ if (guid == MFVideoFormat_DV25)
+ return QStringLiteral("DVCPRO 25 (DV25)");
+ else if (guid == MFVideoFormat_DV50)
+ return QStringLiteral("DVCPRO 50 (DV50)");
+ else if (guid == MFVideoFormat_DVC)
+ return QStringLiteral("DVC/DV Video");
+ else if (guid == MFVideoFormat_DVH1)
+ return QStringLiteral("DVCPRO 100 (DVH1)");
+ else if (guid == MFVideoFormat_DVHD)
+ return QStringLiteral("HD-DVCR (DVHD)");
+ else if (guid == MFVideoFormat_DVSD)
+ return QStringLiteral("SDL-DVCR (DVSD)");
+ else if (guid == MFVideoFormat_DVSL)
+ return QStringLiteral("SD-DVCR (DVSL)");
+ else if (guid == MFVideoFormat_H264)
+ return QStringLiteral("H.264 Video");
+ else if (guid == MFVideoFormat_M4S2)
+ return QStringLiteral("MPEG-4 part 2 Video (M4S2)");
+ else if (guid == MFVideoFormat_MJPG)
+ return QStringLiteral("Motion JPEG (MJPG)");
+ else if (guid == MFVideoFormat_MP43)
+ return QStringLiteral("Microsoft MPEG 4 version 3 (MP43)");
+ else if (guid == MFVideoFormat_MP4S)
+ return QStringLiteral("ISO MPEG 4 version 1 (MP4S)");
+ else if (guid == MFVideoFormat_MP4V)
+ return QStringLiteral("MPEG-4 part 2 Video (MP4V)");
+ else if (guid == MFVideoFormat_MPEG2)
+ return QStringLiteral("MPEG-2 Video");
+ else if (guid == MFVideoFormat_MPG1)
+ return QStringLiteral("MPEG-1 Video");
+ else if (guid == MFVideoFormat_MSS1)
+ return QStringLiteral("Windows Media Screen 1 (MSS1)");
+ else if (guid == MFVideoFormat_MSS2)
+ return QStringLiteral("Windows Media Video 9 Screen (MSS2)");
+ else if (guid == MFVideoFormat_WMV1)
+ return QStringLiteral("Windows Media Video 7 (WMV1)");
+ else if (guid == MFVideoFormat_WMV2)
+ return QStringLiteral("Windows Media Video 8 (WMV2)");
+ else if (guid == MFVideoFormat_WMV3)
+ return QStringLiteral("Windows Media Video 9 (WMV3)");
+ else if (guid == MFVideoFormat_WVC1)
+ return QStringLiteral("Windows Media Video VC1 (WVC1)");
+
+ else
+ return QStringLiteral("Unknown codec");
+}
+
+MFMetaDataControl::MFMetaDataControl(QObject *parent)
+ : QMetaDataReaderControl(parent)
+ , m_metaData(0)
+ , m_content(0)
+{
+}
+
+MFMetaDataControl::~MFMetaDataControl()
+{
+ if (m_metaData)
+ m_metaData->Release();
+ if (m_content)
+ m_content->Release();
+}
+
+bool MFMetaDataControl::isMetaDataAvailable() const
+{
+ return m_content || m_metaData;
+}
+
+QVariant MFMetaDataControl::metaData(const QString &key) const
+{
+ QVariant value;
+ if (!isMetaDataAvailable())
+ return value;
+
+ int index = m_availableMetaDatas.indexOf(key);
+ if (index < 0)
+ return value;
+
+ PROPVARIANT var;
+ PropVariantInit(&var);
+ HRESULT hr = S_FALSE;
+ if (m_content)
+ hr = m_content->GetValue(m_commonKeys[index], &var);
+ else if (m_metaData)
+ hr = m_metaData->GetProperty(reinterpret_cast<LPCWSTR>(m_commonNames[index].utf16()), &var);
+
+ if (SUCCEEDED(hr)) {
+ value = convertValue(var);
+
+ // some metadata needs to be reformatted
+ if (value.isValid() && m_content) {
+ if (key == QMediaMetaData::MediaType) {
+ QString v = value.toString();
+ if (v == QLatin1String("{D1607DBC-E323-4BE2-86A1-48A42A28441E}"))
+ value = QStringLiteral("Music");
+ else if (v == QLatin1String("{DB9830BD-3AB3-4FAB-8A37-1A995F7FF74B}"))
+ value = QStringLiteral("Video");
+ else if (v == QLatin1String("{01CD0F29-DA4E-4157-897B-6275D50C4F11}"))
+ value = QStringLiteral("Audio");
+ else if (v == QLatin1String("{FCF24A76-9A57-4036-990D-E35DD8B244E1}"))
+ value = QStringLiteral("Other");
+ } else if (key == QMediaMetaData::Duration) {
+ // duration is provided in 100-nanosecond units, convert to milliseconds
+ value = (value.toLongLong() + 10000) / 10000;
+ } else if (key == QMediaMetaData::AudioCodec || key == QMediaMetaData::VideoCodec) {
+ GUID guid;
+ if (SUCCEEDED(CLSIDFromString((const WCHAR*)value.toString().utf16(), &guid)))
+ value = nameForGUID(guid);
+ } else if (key == QMediaMetaData::Resolution) {
+ QSize res;
+ res.setHeight(value.toUInt());
+ if (m_content && SUCCEEDED(m_content->GetValue(PKEY_Video_FrameWidth, &var)))
+ res.setWidth(convertValue(var).toUInt());
+ value = res;
+ } else if (key == QMediaMetaData::Orientation) {
+ uint orientation = 0;
+ if (m_content && SUCCEEDED(m_content->GetValue(PKEY_Video_Orientation, &var)))
+ orientation = convertValue(var).toUInt();
+ value = orientation;
+ } else if (key == QMediaMetaData::PixelAspectRatio) {
+ QSize aspectRatio;
+ aspectRatio.setWidth(value.toUInt());
+ if (m_content && SUCCEEDED(m_content->GetValue(PKEY_Video_VerticalAspectRatio, &var)))
+ aspectRatio.setHeight(convertValue(var).toUInt());
+ value = aspectRatio;
+ } else if (key == QMediaMetaData::VideoFrameRate) {
+ value = value.toReal() / 1000.f;
+ }
+ }
+ }
+
+ PropVariantClear(&var);
+ return value;
+}
+
+QVariant MFMetaDataControl::convertValue(const PROPVARIANT& var) const
+{
+ QVariant value;
+ switch (var.vt) {
+ case VT_LPWSTR:
+ value = QString::fromUtf16(reinterpret_cast<const ushort*>(var.pwszVal));
+ break;
+ case VT_UI4:
+ value = uint(var.ulVal);
+ break;
+ case VT_UI8:
+ value = qulonglong(var.uhVal.QuadPart);
+ break;
+ case VT_BOOL:
+ value = bool(var.boolVal);
+ break;
+ case VT_FILETIME:
+ SYSTEMTIME sysDate;
+ if (!FileTimeToSystemTime(&var.filetime, &sysDate))
+ break;
+ value = QDate(sysDate.wYear, sysDate.wMonth, sysDate.wDay);
+ break;
+ case VT_STREAM:
+ {
+ STATSTG stat;
+ if (FAILED(var.pStream->Stat(&stat, STATFLAG_NONAME)))
+ break;
+ void *data = malloc(stat.cbSize.QuadPart);
+ ULONG read = 0;
+ if (FAILED(var.pStream->Read(data, stat.cbSize.QuadPart, &read))) {
+ free(data);
+ break;
+ }
+ value = QImage::fromData((const uchar*)data, read);
+ free(data);
+ }
+ break;
+ case VT_VECTOR | VT_LPWSTR:
+ QStringList vList;
+ for (ULONG i = 0; i < var.calpwstr.cElems; ++i)
+ vList.append(QString::fromUtf16(reinterpret_cast<const ushort*>(var.calpwstr.pElems[i])));
+ value = vList;
+ break;
+ }
+ return value;
+}
+
+QStringList MFMetaDataControl::availableMetaData() const
+{
+ return m_availableMetaDatas;
+}
+
+void MFMetaDataControl::updateSource(IMFPresentationDescriptor* sourcePD, IMFMediaSource* mediaSource)
+{
+ if (m_metaData) {
+ m_metaData->Release();
+ m_metaData = 0;
+ }
+
+ if (m_content) {
+ m_content->Release();
+ m_content = 0;
+ }
+
+ m_availableMetaDatas.clear();
+ m_commonKeys.clear();
+ m_commonNames.clear();
+
+ if (SUCCEEDED(MFGetService(mediaSource, MF_PROPERTY_HANDLER_SERVICE, IID_PPV_ARGS(&m_content)))) {
+ DWORD cProps;
+ if (SUCCEEDED(m_content->GetCount(&cProps))) {
+ for (DWORD i = 0; i < cProps; i++)
+ {
+ PROPERTYKEY key;
+ if (FAILED(m_content->GetAt(i, &key)))
+ continue;
+ bool common = true;
+ if (key == PKEY_Author) {
+ m_availableMetaDatas.push_back(QMediaMetaData::Author);
+ } else if (key == PKEY_Title) {
+ m_availableMetaDatas.push_back(QMediaMetaData::Title);
+ } else if (key == PKEY_Media_SubTitle) {
+ m_availableMetaDatas.push_back(QMediaMetaData::SubTitle);
+ } else if (key == PKEY_ParentalRating) {
+ m_availableMetaDatas.push_back(QMediaMetaData::ParentalRating);
+ } else if (key == PKEY_Media_EncodingSettings) {
+ m_availableMetaDatas.push_back(QMediaMetaData::Description);
+ } else if (key == PKEY_Copyright) {
+ m_availableMetaDatas.push_back(QMediaMetaData::Copyright);
+ } else if (key == PKEY_Comment) {
+ m_availableMetaDatas.push_back(QMediaMetaData::Comment);
+ } else if (key == PKEY_Media_ProviderStyle) {
+ m_availableMetaDatas.push_back(QMediaMetaData::Genre);
+ } else if (key == PKEY_Media_Year) {
+ m_availableMetaDatas.push_back(QMediaMetaData::Year);
+ } else if (key == PKEY_Media_DateEncoded) {
+ m_availableMetaDatas.push_back(QMediaMetaData::Date);
+ } else if (key == PKEY_Rating) {
+ m_availableMetaDatas.push_back(QMediaMetaData::UserRating);
+ } else if (key == PKEY_Keywords) {
+ m_availableMetaDatas.push_back(QMediaMetaData::Keywords);
+ } else if (key == PKEY_Language) {
+ m_availableMetaDatas.push_back(QMediaMetaData::Language);
+ } else if (key == PKEY_Media_Publisher) {
+ m_availableMetaDatas.push_back(QMediaMetaData::Publisher);
+ } else if (key == PKEY_Media_ClassPrimaryID) {
+ m_availableMetaDatas.push_back(QMediaMetaData::MediaType);
+ } else if (key == PKEY_Media_Duration) {
+ m_availableMetaDatas.push_back(QMediaMetaData::Duration);
+ } else if (key == PKEY_Audio_EncodingBitrate) {
+ m_availableMetaDatas.push_back(QMediaMetaData::AudioBitRate);
+ } else if (key == PKEY_Audio_Format) {
+ m_availableMetaDatas.push_back(QMediaMetaData::AudioCodec);
+ } else if (key == PKEY_Media_AverageLevel) {
+ m_availableMetaDatas.push_back(QMediaMetaData::AverageLevel);
+ } else if (key == PKEY_Audio_ChannelCount) {
+ m_availableMetaDatas.push_back(QMediaMetaData::ChannelCount);
+ } else if (key == PKEY_Audio_PeakValue) {
+ m_availableMetaDatas.push_back(QMediaMetaData::PeakValue);
+ } else if (key == PKEY_Audio_SampleRate) {
+ m_availableMetaDatas.push_back(QMediaMetaData::SampleRate);
+ } else if (key == PKEY_Music_AlbumTitle) {
+ m_availableMetaDatas.push_back(QMediaMetaData::AlbumTitle);
+ } else if (key == PKEY_Music_AlbumArtist) {
+ m_availableMetaDatas.push_back(QMediaMetaData::AlbumArtist);
+ } else if (key == PKEY_Music_Artist) {
+ m_availableMetaDatas.push_back(QMediaMetaData::ContributingArtist);
+ } else if (key == PKEY_Music_Composer) {
+ m_availableMetaDatas.push_back(QMediaMetaData::Composer);
+ } else if (key == PKEY_Music_Conductor) {
+ m_availableMetaDatas.push_back(QMediaMetaData::Conductor);
+ } else if (key == PKEY_Music_Lyrics) {
+ m_availableMetaDatas.push_back(QMediaMetaData::Lyrics);
+ } else if (key == PKEY_Music_Mood) {
+ m_availableMetaDatas.push_back(QMediaMetaData::Mood);
+ } else if (key == PKEY_Music_TrackNumber) {
+ m_availableMetaDatas.push_back(QMediaMetaData::TrackNumber);
+ } else if (key == PKEY_Music_Genre) {
+ m_availableMetaDatas.push_back(QMediaMetaData::Genre);
+ } else if (key == PKEY_ThumbnailStream) {
+ m_availableMetaDatas.push_back(QMediaMetaData::ThumbnailImage);
+ } else if (key == PKEY_Video_FrameHeight) {
+ m_availableMetaDatas.push_back(QMediaMetaData::Resolution);
+ } else if (key == PKEY_Video_Orientation) {
+ m_availableMetaDatas.push_back(QMediaMetaData::Orientation);
+ } else if (key == PKEY_Video_HorizontalAspectRatio) {
+ m_availableMetaDatas.push_back(QMediaMetaData::PixelAspectRatio);
+ } else if (key == PKEY_Video_FrameRate) {
+ m_availableMetaDatas.push_back(QMediaMetaData::VideoFrameRate);
+ } else if (key == PKEY_Video_EncodingBitrate) {
+ m_availableMetaDatas.push_back(QMediaMetaData::VideoBitRate);
+ } else if (key == PKEY_Video_Compression) {
+ m_availableMetaDatas.push_back(QMediaMetaData::VideoCodec);
+ } else if (key == PKEY_Video_Director) {
+ m_availableMetaDatas.push_back(QMediaMetaData::Director);
+ } else if (key == PKEY_Media_Writer) {
+ m_availableMetaDatas.push_back(QMediaMetaData::Writer);
+ } else {
+ common = false;
+ //TODO: add more extended keys
+ }
+ if (common)
+ m_commonKeys.push_back(key);
+ }
+ } else {
+ m_content->Release();
+ m_content = NULL;
+ }
+ }
+
+ if (!m_content) {
+ //fallback to Vista approach
+ IMFMetadataProvider *provider = NULL;
+ if (SUCCEEDED(MFGetService(mediaSource, MF_METADATA_PROVIDER_SERVICE, IID_PPV_ARGS(&provider)))) {
+ if (SUCCEEDED(provider->GetMFMetadata(sourcePD, 0, 0, &m_metaData))) {
+ PROPVARIANT varNames;
+ PropVariantInit(&varNames);
+ if (SUCCEEDED(m_metaData->GetAllPropertyNames(&varNames)) && varNames.vt == (VT_VECTOR | VT_LPWSTR)) {
+ ULONG cElements = varNames.calpwstr.cElems;
+ for (ULONG i = 0; i < cElements; i++)
+ {
+ const WCHAR* sName = varNames.calpwstr.pElems[i];
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "metadata: " << QString::fromUtf16(sName);
+#endif
+ if (wcscmp(sName, L"Author") == 0) {
+ m_availableMetaDatas.push_back(QMediaMetaData::Author);
+ } else if (wcscmp(sName, L"Title") == 0) {
+ m_availableMetaDatas.push_back(QMediaMetaData::Title);
+ } else if (wcscmp(sName, L"Rating") == 0) {
+ m_availableMetaDatas.push_back(QMediaMetaData::ParentalRating);
+ } else if (wcscmp(sName, L"Description") == 0) {
+ m_availableMetaDatas.push_back(QMediaMetaData::Description);
+ } else if (wcscmp(sName, L"Copyright") == 0) {
+ m_availableMetaDatas.push_back(QMediaMetaData::Copyright);
+ //TODO: add more common keys
+ } else {
+ m_availableMetaDatas.push_back(QString::fromUtf16(reinterpret_cast<const ushort*>(sName)));
+ }
+ m_commonNames.push_back(QString::fromUtf16(reinterpret_cast<const ushort*>(sName)));
+ }
+ }
+ PropVariantClear(&varNames);
+ } else {
+ qWarning("Failed to get IMFMetadata");
+ }
+ provider->Release();
+ } else {
+ qWarning("Failed to get IMFMetadataProvider from source");
+ }
+ }
+
+ emit metaDataChanged();
+ emit metaDataAvailableChanged(m_metaData || m_content);
+}
diff --git a/src/multimedia/platform/wmf/player/mfmetadatacontrol_p.h b/src/multimedia/platform/wmf/player/mfmetadatacontrol_p.h
new file mode 100644
index 000000000..dcce5bb1d
--- /dev/null
+++ b/src/multimedia/platform/wmf/player/mfmetadatacontrol_p.h
@@ -0,0 +1,83 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef MFMETADATACONTROL_H
+#define MFMETADATACONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <qmetadatareadercontrol.h>
+#include "Mfidl.h"
+
+QT_USE_NAMESPACE
+
+class MFMetaDataControl : public QMetaDataReaderControl
+{
+ Q_OBJECT
+public:
+ MFMetaDataControl(QObject *parent = 0);
+ ~MFMetaDataControl();
+
+ bool isMetaDataAvailable() const;
+
+ QVariant metaData(const QString &key) const;
+ QStringList availableMetaData() const;
+
+ void updateSource(IMFPresentationDescriptor* sourcePD, IMFMediaSource* mediaSource);
+
+private:
+ QVariant convertValue(const PROPVARIANT& var) const;
+ IPropertyStore *m_content; //for Windows7
+ IMFMetadata *m_metaData; //for Vista
+
+ QStringList m_availableMetaDatas;
+ QList<PROPERTYKEY> m_commonKeys; //for Windows7
+ QStringList m_commonNames; //for Vista
+};
+
+#endif
diff --git a/src/multimedia/platform/wmf/player/mfplayercontrol.cpp b/src/multimedia/platform/wmf/player/mfplayercontrol.cpp
new file mode 100644
index 000000000..3bb963417
--- /dev/null
+++ b/src/multimedia/platform/wmf/player/mfplayercontrol.cpp
@@ -0,0 +1,316 @@
+/****************************************************************************
+**
+** 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 "mfplayercontrol_p.h"
+#include <qtcore/qdebug.h>
+
+//#define DEBUG_MEDIAFOUNDATION
+
+MFPlayerControl::MFPlayerControl(MFPlayerSession *session)
+: QMediaPlayerControl(session)
+, m_state(QMediaPlayer::StoppedState)
+, m_stateDirty(false)
+, m_videoAvailable(false)
+, m_audioAvailable(false)
+, m_duration(-1)
+, m_seekable(false)
+, m_session(session)
+{
+ QObject::connect(m_session, SIGNAL(statusChanged()), this, SLOT(handleStatusChanged()));
+ QObject::connect(m_session, SIGNAL(videoAvailable()), this, SLOT(handleVideoAvailable()));
+ QObject::connect(m_session, SIGNAL(audioAvailable()), this, SLOT(handleAudioAvailable()));
+ QObject::connect(m_session, SIGNAL(durationUpdate(qint64)), this, SLOT(handleDurationUpdate(qint64)));
+ QObject::connect(m_session, SIGNAL(seekableUpdate(bool)), this, SLOT(handleSeekableUpdate(bool)));
+ QObject::connect(m_session, SIGNAL(error(QMediaPlayer::Error,QString,bool)), this, SLOT(handleError(QMediaPlayer::Error,QString,bool)));
+ QObject::connect(m_session, SIGNAL(positionChanged(qint64)), this, SIGNAL(positionChanged(qint64)));
+ QObject::connect(m_session, SIGNAL(volumeChanged(int)), this, SIGNAL(volumeChanged(int)));
+ QObject::connect(m_session, SIGNAL(mutedChanged(bool)), this, SIGNAL(mutedChanged(bool)));
+ QObject::connect(m_session, SIGNAL(playbackRateChanged(qreal)), this, SIGNAL(playbackRateChanged(qreal)));
+ QObject::connect(m_session, SIGNAL(bufferStatusChanged(int)), this, SIGNAL(bufferStatusChanged(int)));
+}
+
+MFPlayerControl::~MFPlayerControl()
+{
+}
+
+void MFPlayerControl::setMedia(const QUrl &media, QIODevice *stream)
+{
+ if (m_state != QMediaPlayer::StoppedState) {
+ changeState(QMediaPlayer::StoppedState);
+ m_session->stop(true);
+ refreshState();
+ }
+
+ m_media = media;
+ m_stream = stream;
+ resetAudioVideoAvailable();
+ handleDurationUpdate(-1);
+ handleSeekableUpdate(false);
+ m_session->load(media, stream);
+ emit mediaChanged(m_media);
+}
+
+void MFPlayerControl::play()
+{
+ if (m_state == QMediaPlayer::PlayingState)
+ return;
+ if (QMediaPlayer::InvalidMedia == m_session->status())
+ m_session->load(m_media, m_stream);
+
+ switch (m_session->status()) {
+ case QMediaPlayer::UnknownMediaStatus:
+ case QMediaPlayer::NoMedia:
+ case QMediaPlayer::InvalidMedia:
+ return;
+ case QMediaPlayer::LoadedMedia:
+ case QMediaPlayer::BufferingMedia:
+ case QMediaPlayer::BufferedMedia:
+ case QMediaPlayer::EndOfMedia:
+ changeState(QMediaPlayer::PlayingState);
+ m_session->start();
+ break;
+ default: //Loading/Stalled
+ changeState(QMediaPlayer::PlayingState);
+ break;
+ }
+ refreshState();
+}
+
+void MFPlayerControl::pause()
+{
+ if (m_state != QMediaPlayer::PlayingState)
+ return;
+ changeState(QMediaPlayer::PausedState);
+ m_session->pause();
+ refreshState();
+}
+
+void MFPlayerControl::stop()
+{
+ if (m_state == QMediaPlayer::StoppedState)
+ return;
+ changeState(QMediaPlayer::StoppedState);
+ m_session->stop();
+ refreshState();
+}
+
+void MFPlayerControl::changeState(QMediaPlayer::State state)
+{
+ if (m_state == state)
+ return;
+ m_state = state;
+ m_stateDirty = true;
+}
+
+void MFPlayerControl::refreshState()
+{
+ if (!m_stateDirty)
+ return;
+ m_stateDirty = false;
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "MFPlayerControl::emit stateChanged" << m_state;
+#endif
+ emit stateChanged(m_state);
+}
+
+void MFPlayerControl::handleStatusChanged()
+{
+ QMediaPlayer::MediaStatus status = m_session->status();
+ switch (status) {
+ case QMediaPlayer::EndOfMedia:
+ changeState(QMediaPlayer::StoppedState);
+ break;
+ case QMediaPlayer::InvalidMedia:
+ break;
+ case QMediaPlayer::LoadedMedia:
+ case QMediaPlayer::BufferingMedia:
+ case QMediaPlayer::BufferedMedia:
+ if (m_state == QMediaPlayer::PlayingState)
+ m_session->start();
+ break;
+ }
+ emit mediaStatusChanged(m_session->status());
+ refreshState();
+}
+
+void MFPlayerControl::handleVideoAvailable()
+{
+ if (m_videoAvailable)
+ return;
+ m_videoAvailable = true;
+ emit videoAvailableChanged(m_videoAvailable);
+}
+
+void MFPlayerControl::handleAudioAvailable()
+{
+ if (m_audioAvailable)
+ return;
+ m_audioAvailable = true;
+ emit audioAvailableChanged(m_audioAvailable);
+}
+
+void MFPlayerControl::resetAudioVideoAvailable()
+{
+ bool videoDirty = false;
+ if (m_videoAvailable) {
+ m_videoAvailable = false;
+ videoDirty = true;
+ }
+ if (m_audioAvailable) {
+ m_audioAvailable = false;
+ emit audioAvailableChanged(m_audioAvailable);
+ }
+ if (videoDirty)
+ emit videoAvailableChanged(m_videoAvailable);
+}
+
+void MFPlayerControl::handleDurationUpdate(qint64 duration)
+{
+ if (m_duration == duration)
+ return;
+ m_duration = duration;
+ emit durationChanged(m_duration);
+}
+
+void MFPlayerControl::handleSeekableUpdate(bool seekable)
+{
+ if (m_seekable == seekable)
+ return;
+ m_seekable = seekable;
+ emit seekableChanged(m_seekable);
+}
+
+QMediaPlayer::State MFPlayerControl::state() const
+{
+ return m_state;
+}
+
+QMediaPlayer::MediaStatus MFPlayerControl::mediaStatus() const
+{
+ return m_session->status();
+}
+
+qint64 MFPlayerControl::duration() const
+{
+ return m_duration;
+}
+
+qint64 MFPlayerControl::position() const
+{
+ return m_session->position();
+}
+
+void MFPlayerControl::setPosition(qint64 position)
+{
+ if (!m_seekable || position == m_session->position())
+ return;
+ m_session->setPosition(position);
+}
+
+int MFPlayerControl::volume() const
+{
+ return m_session->volume();
+}
+
+void MFPlayerControl::setVolume(int volume)
+{
+ m_session->setVolume(volume);
+}
+
+bool MFPlayerControl::isMuted() const
+{
+ return m_session->isMuted();
+}
+
+void MFPlayerControl::setMuted(bool muted)
+{
+ m_session->setMuted(muted);
+}
+
+int MFPlayerControl::bufferStatus() const
+{
+ return m_session->bufferStatus();
+}
+
+bool MFPlayerControl::isAudioAvailable() const
+{
+ return m_audioAvailable;
+}
+
+bool MFPlayerControl::isVideoAvailable() const
+{
+ return m_videoAvailable;
+}
+
+bool MFPlayerControl::isSeekable() const
+{
+ return m_seekable;
+}
+
+QMediaTimeRange MFPlayerControl::availablePlaybackRanges() const
+{
+ return m_session->availablePlaybackRanges();
+}
+
+qreal MFPlayerControl::playbackRate() const
+{
+ return m_session->playbackRate();
+}
+
+void MFPlayerControl::setPlaybackRate(qreal rate)
+{
+ m_session->setPlaybackRate(rate);
+}
+
+QUrl MFPlayerControl::media() const
+{
+ return m_media;
+}
+
+const QIODevice* MFPlayerControl::mediaStream() const
+{
+ return m_stream;
+}
+
+void MFPlayerControl::handleError(QMediaPlayer::Error errorCode, const QString& errorString, bool isFatal)
+{
+ if (isFatal)
+ stop();
+ emit error(int(errorCode), errorString);
+}
diff --git a/src/multimedia/platform/wmf/player/mfplayercontrol_p.h b/src/multimedia/platform/wmf/player/mfplayercontrol_p.h
new file mode 100644
index 000000000..35695e0db
--- /dev/null
+++ b/src/multimedia/platform/wmf/player/mfplayercontrol_p.h
@@ -0,0 +1,136 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef MFPLAYERCONTROL_H
+#define MFPLAYERCONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "QUrl.h"
+#include "qmediaplayercontrol.h"
+
+#include <QtCore/qcoreevent.h>
+
+#include "mfplayersession_p.h"
+
+QT_USE_NAMESPACE
+
+class MFPlayerControl : public QMediaPlayerControl
+{
+ Q_OBJECT
+public:
+ MFPlayerControl(MFPlayerSession *session);
+ ~MFPlayerControl();
+
+ QMediaPlayer::State state() const;
+
+ QMediaPlayer::MediaStatus mediaStatus() const;
+
+ qint64 duration() const;
+
+ qint64 position() const;
+ void setPosition(qint64 position);
+
+ int volume() const;
+ void setVolume(int volume);
+
+ bool isMuted() const;
+ void setMuted(bool muted);
+
+ int bufferStatus() const;
+
+ bool isAudioAvailable() const;
+ bool isVideoAvailable() const;
+
+ bool isSeekable() const;
+
+ QMediaTimeRange availablePlaybackRanges() const;
+
+ qreal playbackRate() const;
+ void setPlaybackRate(qreal rate);
+
+ QUrl media() const;
+ const QIODevice *mediaStream() const;
+ void setMedia(const QUrl &media, QIODevice *stream);
+
+ void play();
+ void pause();
+ void stop();
+
+ bool streamPlaybackSupported() const { return true; }
+
+
+private Q_SLOTS:
+ void handleStatusChanged();
+ void handleVideoAvailable();
+ void handleAudioAvailable();
+ void handleDurationUpdate(qint64 duration);
+ void handleSeekableUpdate(bool seekable);
+ void handleError(QMediaPlayer::Error errorCode, const QString& errorString, bool isFatal);
+
+private:
+ void changeState(QMediaPlayer::State state);
+ void resetAudioVideoAvailable();
+ void refreshState();
+
+ QMediaPlayer::State m_state;
+ bool m_stateDirty;
+ QMediaPlayer::MediaStatus m_status;
+ QMediaPlayer::Error m_error;
+
+ bool m_videoAvailable;
+ bool m_audioAvailable;
+ qint64 m_duration;
+ bool m_seekable;
+
+ QIODevice *m_stream;
+ QUrl m_media;
+ MFPlayerSession *m_session;
+};
+
+#endif
diff --git a/src/multimedia/platform/wmf/player/mfplayerservice.cpp b/src/multimedia/platform/wmf/player/mfplayerservice.cpp
new file mode 100644
index 000000000..1fce4f6c3
--- /dev/null
+++ b/src/multimedia/platform/wmf/player/mfplayerservice.cpp
@@ -0,0 +1,167 @@
+/****************************************************************************
+**
+** 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 "QUrl.h"
+
+#include <QtCore/qdebug.h>
+
+#include "mfplayercontrol_p.h"
+#include "mfevrvideowindowcontrol_p.h"
+#include "mfvideorenderercontrol_p.h"
+#include "mfaudioendpointcontrol_p.h"
+#include "mfaudioprobecontrol_p.h"
+#include "mfvideoprobecontrol_p.h"
+#include "mfplayerservice_p.h"
+#include "mfplayersession_p.h"
+#include "mfmetadatacontrol_p.h"
+
+MFPlayerService::MFPlayerService(QObject *parent)
+ : QMediaService(parent)
+ , m_session(0)
+ , m_videoWindowControl(0)
+ , m_videoRendererControl(0)
+{
+ m_audioEndpointControl = new MFAudioEndpointControl(this);
+ m_session = new MFPlayerSession(this);
+ m_player = new MFPlayerControl(m_session);
+ m_metaDataControl = new MFMetaDataControl(this);
+}
+
+MFPlayerService::~MFPlayerService()
+{
+ m_session->close();
+
+ if (m_videoWindowControl)
+ delete m_videoWindowControl;
+
+ if (m_videoRendererControl)
+ delete m_videoRendererControl;
+
+ m_session->Release();
+}
+
+QObject *MFPlayerService::requestControl(const char *name)
+{
+ if (qstrcmp(name, QMediaPlayerControl_iid) == 0) {
+ return m_player;
+ } else if (qstrcmp(name, QAudioOutputSelectorControl_iid) == 0) {
+ return m_audioEndpointControl;
+ } else if (qstrcmp(name, QMetaDataReaderControl_iid) == 0) {
+ return m_metaDataControl;
+ } else if (qstrcmp(name, QVideoRendererControl_iid) == 0) {
+ if (!m_videoRendererControl && !m_videoWindowControl) {
+ m_videoRendererControl = new MFVideoRendererControl;
+ return m_videoRendererControl;
+ }
+ } else if (qstrcmp(name, QVideoWindowControl_iid) == 0) {
+ if (!m_videoRendererControl && !m_videoWindowControl) {
+ m_videoWindowControl = new MFEvrVideoWindowControl;
+ return m_videoWindowControl;
+ }
+ } else if (qstrcmp(name,QMediaAudioProbeControl_iid) == 0) {
+ if (m_session) {
+ MFAudioProbeControl *probe = new MFAudioProbeControl(this);
+ m_session->addProbe(probe);
+ return probe;
+ }
+ return 0;
+ } else if (qstrcmp(name,QMediaVideoProbeControl_iid) == 0) {
+ if (m_session) {
+ MFVideoProbeControl *probe = new MFVideoProbeControl(this);
+ m_session->addProbe(probe);
+ return probe;
+ }
+ return 0;
+ }
+
+ return 0;
+}
+
+void MFPlayerService::releaseControl(QObject *control)
+{
+ if (!control) {
+ qWarning("QMediaService::releaseControl():"
+ " Attempted release of null control");
+ } else if (control == m_videoRendererControl) {
+ m_videoRendererControl->setSurface(0);
+ delete m_videoRendererControl;
+ m_videoRendererControl = 0;
+ return;
+ } else if (control == m_videoWindowControl) {
+ delete m_videoWindowControl;
+ m_videoWindowControl = 0;
+ return;
+ }
+
+ MFAudioProbeControl* audioProbe = qobject_cast<MFAudioProbeControl*>(control);
+ if (audioProbe) {
+ if (m_session)
+ m_session->removeProbe(audioProbe);
+ delete audioProbe;
+ return;
+ }
+
+ MFVideoProbeControl* videoProbe = qobject_cast<MFVideoProbeControl*>(control);
+ if (videoProbe) {
+ if (m_session)
+ m_session->removeProbe(videoProbe);
+ delete videoProbe;
+ return;
+ }
+}
+
+MFAudioEndpointControl* MFPlayerService::audioEndpointControl() const
+{
+ return m_audioEndpointControl;
+}
+
+MFVideoRendererControl* MFPlayerService::videoRendererControl() const
+{
+ return m_videoRendererControl;
+}
+
+MFEvrVideoWindowControl* MFPlayerService::videoWindowControl() const
+{
+ return m_videoWindowControl;
+}
+
+MFMetaDataControl* MFPlayerService::metaDataControl() const
+{
+ return m_metaDataControl;
+}
diff --git a/src/multimedia/platform/wmf/player/mfplayerservice_p.h b/src/multimedia/platform/wmf/player/mfplayerservice_p.h
new file mode 100644
index 000000000..50362c381
--- /dev/null
+++ b/src/multimedia/platform/wmf/player/mfplayerservice_p.h
@@ -0,0 +1,98 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef MFPLAYERSERVICE_H
+#define MFPLAYERSERVICE_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <mfapi.h>
+#include <mfidl.h>
+
+#include "qmediaplayer.h"
+#include "qmediaservice.h"
+#include "qmediatimerange.h"
+
+QT_BEGIN_NAMESPACE
+class QUrl;
+QT_END_NAMESPACE
+
+QT_USE_NAMESPACE
+
+class MFEvrVideoWindowControl;
+class MFAudioEndpointControl;
+class MFVideoRendererControl;
+class MFPlayerControl;
+class MFMetaDataControl;
+class MFPlayerSession;
+
+class MFPlayerService : public QMediaService
+{
+ Q_OBJECT
+public:
+ MFPlayerService(QObject *parent = 0);
+ ~MFPlayerService();
+
+ QObject *requestControl(const char *name);
+ void releaseControl(QObject *control);
+
+ MFAudioEndpointControl* audioEndpointControl() const;
+ MFVideoRendererControl* videoRendererControl() const;
+ MFEvrVideoWindowControl* videoWindowControl() const;
+ MFMetaDataControl* metaDataControl() const;
+
+private:
+ MFPlayerSession *m_session;
+ MFVideoRendererControl *m_videoRendererControl;
+ MFAudioEndpointControl *m_audioEndpointControl;
+ MFEvrVideoWindowControl *m_videoWindowControl;
+ MFPlayerControl *m_player;
+ MFMetaDataControl *m_metaDataControl;
+};
+
+#endif
diff --git a/src/multimedia/platform/wmf/player/mfplayersession.cpp b/src/multimedia/platform/wmf/player/mfplayersession.cpp
new file mode 100644
index 000000000..0d1213c84
--- /dev/null
+++ b/src/multimedia/platform/wmf/player/mfplayersession.cpp
@@ -0,0 +1,1816 @@
+/****************************************************************************
+**
+** 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 "qmediaplayercontrol.h"
+
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qdatetime.h>
+#include <QtCore/qthread.h>
+#include <QtCore/qvarlengtharray.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qbuffer.h>
+
+#include "mfplayercontrol_p.h"
+#include "mfevrvideowindowcontrol_p.h"
+#include "mfvideorenderercontrol_p.h"
+#include "mfaudioendpointcontrol_p.h"
+
+#include "mfplayersession_p.h"
+#include "mfplayerservice_p.h"
+#include "mfmetadatacontrol_p.h"
+#include <mferror.h>
+#include <nserror.h>
+#include "private/sourceresolver_p.h"
+#include "samplegrabber_p.h"
+#include "mftvideo_p.h"
+#include <wmcodecdsp.h>
+
+//#define DEBUG_MEDIAFOUNDATION
+
+MFPlayerSession::MFPlayerSession(MFPlayerService *playerService)
+ : m_playerService(playerService)
+ , m_cRef(1)
+ , m_session(0)
+ , m_presentationClock(0)
+ , m_rateControl(0)
+ , m_rateSupport(0)
+ , m_volumeControl(0)
+ , m_netsourceStatistics(0)
+ , m_duration(0)
+ , m_sourceResolver(0)
+ , m_hCloseEvent(0)
+ , m_closing(false)
+ , m_pendingRate(1)
+ , m_volume(100)
+ , m_muted(false)
+ , m_status(QMediaPlayer::NoMedia)
+ , m_scrubbing(false)
+ , m_restoreRate(1)
+ , m_mediaTypes(0)
+ , m_audioSampleGrabber(0)
+ , m_audioSampleGrabberNode(0)
+ , m_videoProbeMFT(0)
+{
+ QObject::connect(this, SIGNAL(sessionEvent(IMFMediaEvent*)), this, SLOT(handleSessionEvent(IMFMediaEvent*)));
+
+ m_pendingState = NoPending;
+ ZeroMemory(&m_state, sizeof(m_state));
+ m_state.command = CmdStop;
+ m_state.prevCmd = CmdNone;
+ m_state.rate = 1.0f;
+ ZeroMemory(&m_request, sizeof(m_request));
+ m_request.command = CmdNone;
+ m_request.prevCmd = CmdNone;
+ m_request.rate = 1.0f;
+
+ m_audioSampleGrabber = new AudioSampleGrabberCallback;
+}
+
+void MFPlayerSession::close()
+{
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "close";
+#endif
+
+ clear();
+ if (!m_session)
+ return;
+
+ HRESULT hr = S_OK;
+ if (m_session) {
+ m_closing = true;
+ hr = m_session->Close();
+ if (SUCCEEDED(hr)) {
+ DWORD dwWaitResult = WaitForSingleObject(m_hCloseEvent, 100);
+ if (dwWaitResult == WAIT_TIMEOUT) {
+ qWarning() << "session close time out!";
+ }
+ }
+ m_closing = false;
+ }
+
+ if (SUCCEEDED(hr)) {
+ if (m_session)
+ m_session->Shutdown();
+ if (m_sourceResolver)
+ m_sourceResolver->shutdown();
+ }
+ if (m_sourceResolver) {
+ m_sourceResolver->Release();
+ m_sourceResolver = 0;
+ }
+ if (m_videoProbeMFT) {
+ m_videoProbeMFT->Release();
+ m_videoProbeMFT = 0;
+ }
+
+ if (m_playerService->videoRendererControl()) {
+ m_playerService->videoRendererControl()->releaseActivate();
+ } else if (m_playerService->videoWindowControl()) {
+ m_playerService->videoWindowControl()->releaseActivate();
+ }
+
+ if (m_session)
+ m_session->Release();
+ m_session = 0;
+ if (m_hCloseEvent)
+ CloseHandle(m_hCloseEvent);
+ m_hCloseEvent = 0;
+}
+
+void MFPlayerSession::addProbe(MFAudioProbeControl *probe)
+{
+ m_audioSampleGrabber->addProbe(probe);
+}
+
+void MFPlayerSession::removeProbe(MFAudioProbeControl *probe)
+{
+ m_audioSampleGrabber->removeProbe(probe);
+}
+
+void MFPlayerSession::addProbe(MFVideoProbeControl* probe)
+{
+ if (m_videoProbes.contains(probe))
+ return;
+
+ m_videoProbes.append(probe);
+
+ if (m_videoProbeMFT)
+ m_videoProbeMFT->addProbe(probe);
+}
+
+void MFPlayerSession::removeProbe(MFVideoProbeControl* probe)
+{
+ m_videoProbes.removeOne(probe);
+
+ if (m_videoProbeMFT)
+ m_videoProbeMFT->removeProbe(probe);
+}
+
+MFPlayerSession::~MFPlayerSession()
+{
+ m_audioSampleGrabber->Release();
+}
+
+
+void MFPlayerSession::load(const QUrl &url, QIODevice *stream)
+{
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "load";
+#endif
+ clear();
+
+ if (m_status == QMediaPlayer::LoadingMedia && m_sourceResolver)
+ m_sourceResolver->cancel();
+
+ if (url.isEmpty() && !stream) {
+ changeStatus(QMediaPlayer::NoMedia);
+ } else if (stream && (!stream->isReadable())) {
+ changeStatus(QMediaPlayer::InvalidMedia);
+ emit error(QMediaPlayer::ResourceError, tr("Invalid stream source."), true);
+ } else {
+ createSession();
+ changeStatus(QMediaPlayer::LoadingMedia);
+ m_sourceResolver->load(url, stream);
+ }
+ emit positionChanged(position());
+}
+
+void MFPlayerSession::handleSourceError(long hr)
+{
+ QString errorString;
+ QMediaPlayer::Error errorCode = QMediaPlayer::ResourceError;
+ switch (hr) {
+ case QMediaPlayer::FormatError:
+ errorCode = QMediaPlayer::FormatError;
+ errorString = tr("Attempting to play invalid Qt resource.");
+ break;
+ case NS_E_FILE_NOT_FOUND:
+ errorString = tr("The system cannot find the file specified.");
+ break;
+ case NS_E_SERVER_NOT_FOUND:
+ errorString = tr("The specified server could not be found.");
+ break;
+ case MF_E_UNSUPPORTED_BYTESTREAM_TYPE:
+ errorCode = QMediaPlayer::FormatError;
+ errorString = tr("Unsupported media type.");
+ break;
+ default:
+ errorString = tr("Failed to load source.");
+ break;
+ }
+ changeStatus(QMediaPlayer::InvalidMedia);
+ emit error(errorCode, errorString, true);
+}
+
+void MFPlayerSession::handleMediaSourceReady()
+{
+ if (QMediaPlayer::LoadingMedia != m_status || !m_sourceResolver || m_sourceResolver != sender())
+ return;
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "handleMediaSourceReady";
+#endif
+ HRESULT hr = S_OK;
+ IMFMediaSource* mediaSource = m_sourceResolver->mediaSource();
+
+ DWORD dwCharacteristics = 0;
+ mediaSource->GetCharacteristics(&dwCharacteristics);
+ emit seekableUpdate(MFMEDIASOURCE_CAN_SEEK & dwCharacteristics);
+
+ IMFPresentationDescriptor* sourcePD;
+ hr = mediaSource->CreatePresentationDescriptor(&sourcePD);
+ if (SUCCEEDED(hr)) {
+ m_duration = 0;
+ m_playerService->metaDataControl()->updateSource(sourcePD, mediaSource);
+ sourcePD->GetUINT64(MF_PD_DURATION, &m_duration);
+ //convert from 100 nanosecond to milisecond
+ emit durationUpdate(qint64(m_duration / 10000));
+ setupPlaybackTopology(mediaSource, sourcePD);
+ sourcePD->Release();
+ } else {
+ changeStatus(QMediaPlayer::InvalidMedia);
+ emit error(QMediaPlayer::ResourceError, tr("Cannot create presentation descriptor."), true);
+ }
+}
+
+MFPlayerSession::MediaType MFPlayerSession::getStreamType(IMFStreamDescriptor *stream) const
+{
+ if (!stream)
+ return Unknown;
+
+ struct SafeRelease {
+ IMFMediaTypeHandler *ptr = nullptr;
+ ~SafeRelease() { if (ptr) ptr->Release(); }
+ } typeHandler;
+ if (SUCCEEDED(stream->GetMediaTypeHandler(&typeHandler.ptr))) {
+ GUID guidMajorType;
+ if (SUCCEEDED(typeHandler.ptr->GetMajorType(&guidMajorType))) {
+ if (guidMajorType == MFMediaType_Audio)
+ return Audio;
+ else if (guidMajorType == MFMediaType_Video)
+ return Video;
+ }
+ }
+
+ return Unknown;
+}
+
+void MFPlayerSession::setupPlaybackTopology(IMFMediaSource *source, IMFPresentationDescriptor *sourcePD)
+{
+ HRESULT hr = S_OK;
+ // Get the number of streams in the media source.
+ DWORD cSourceStreams = 0;
+ hr = sourcePD->GetStreamDescriptorCount(&cSourceStreams);
+ if (FAILED(hr)) {
+ changeStatus(QMediaPlayer::UnknownMediaStatus);
+ emit error(QMediaPlayer::ResourceError, tr("Failed to get stream count."), true);
+ return;
+ }
+
+ IMFTopology *topology;
+ hr = MFCreateTopology(&topology);
+ if (FAILED(hr)) {
+ changeStatus(QMediaPlayer::UnknownMediaStatus);
+ emit error(QMediaPlayer::ResourceError, tr("Failed to create topology."), true);
+ return;
+ }
+
+ // Remember output node id for a first video stream
+ TOPOID outputNodeId = -1;
+
+ // For each stream, create the topology nodes and add them to the topology.
+ DWORD succeededCount = 0;
+ for (DWORD i = 0; i < cSourceStreams; i++)
+ {
+ BOOL fSelected = FALSE;
+ bool streamAdded = false;
+ IMFStreamDescriptor *streamDesc = NULL;
+
+ HRESULT hr = sourcePD->GetStreamDescriptorByIndex(i, &fSelected, &streamDesc);
+ if (SUCCEEDED(hr)) {
+ // The media might have multiple audio and video streams,
+ // only use one of each kind, and only if it is selected by default.
+ MediaType mediaType = getStreamType(streamDesc);
+ if (mediaType != Unknown
+ && ((m_mediaTypes & mediaType) == 0) // Check if this type isn't already added
+ && fSelected) {
+
+ IMFTopologyNode *sourceNode = addSourceNode(topology, source, sourcePD, streamDesc);
+ if (sourceNode) {
+ IMFTopologyNode *outputNode = addOutputNode(mediaType, topology, 0);
+ if (outputNode) {
+ bool connected = false;
+ if (mediaType == Audio) {
+ if (!m_audioSampleGrabberNode)
+ connected = setupAudioSampleGrabber(topology, sourceNode, outputNode);
+ } else if (mediaType == Video && outputNodeId == -1) {
+ // Remember video output node ID.
+ outputNode->GetTopoNodeID(&outputNodeId);
+ }
+
+ if (!connected)
+ hr = sourceNode->ConnectOutput(0, outputNode, 0);
+
+ if (FAILED(hr)) {
+ emit error(QMediaPlayer::FormatError, tr("Unable to play any stream."), false);
+ } else {
+ streamAdded = true;
+ succeededCount++;
+ m_mediaTypes |= mediaType;
+ switch (mediaType) {
+ case Audio:
+ emit audioAvailable();
+ break;
+ case Video:
+ emit videoAvailable();
+ break;
+ }
+ }
+ outputNode->Release();
+ }
+ sourceNode->Release();
+ }
+ }
+
+ if (fSelected && !streamAdded)
+ sourcePD->DeselectStream(i);
+
+ streamDesc->Release();
+ }
+ }
+
+ if (succeededCount == 0) {
+ changeStatus(QMediaPlayer::InvalidMedia);
+ emit error(QMediaPlayer::ResourceError, tr("Unable to play."), true);
+ } else {
+ if (outputNodeId != -1) {
+ topology = insertMFT(topology, outputNodeId);
+ }
+
+ hr = m_session->SetTopology(MFSESSION_SETTOPOLOGY_IMMEDIATE, topology);
+ if (FAILED(hr)) {
+ changeStatus(QMediaPlayer::UnknownMediaStatus);
+ emit error(QMediaPlayer::ResourceError, tr("Failed to set topology."), true);
+ }
+ }
+ topology->Release();
+}
+
+IMFTopologyNode* MFPlayerSession::addSourceNode(IMFTopology* topology, IMFMediaSource* source,
+ IMFPresentationDescriptor* presentationDesc, IMFStreamDescriptor *streamDesc)
+{
+ IMFTopologyNode *node = NULL;
+ HRESULT hr = MFCreateTopologyNode(MF_TOPOLOGY_SOURCESTREAM_NODE, &node);
+ if (SUCCEEDED(hr)) {
+ hr = node->SetUnknown(MF_TOPONODE_SOURCE, source);
+ if (SUCCEEDED(hr)) {
+ hr = node->SetUnknown(MF_TOPONODE_PRESENTATION_DESCRIPTOR, presentationDesc);
+ if (SUCCEEDED(hr)) {
+ hr = node->SetUnknown(MF_TOPONODE_STREAM_DESCRIPTOR, streamDesc);
+ if (SUCCEEDED(hr)) {
+ hr = topology->AddNode(node);
+ if (SUCCEEDED(hr))
+ return node;
+ }
+ }
+ }
+ node->Release();
+ }
+ return NULL;
+}
+
+IMFTopologyNode* MFPlayerSession::addOutputNode(MediaType mediaType, IMFTopology* topology, DWORD sinkID)
+{
+ IMFTopologyNode *node = NULL;
+ if (FAILED(MFCreateTopologyNode(MF_TOPOLOGY_OUTPUT_NODE, &node)))
+ return NULL;
+
+ IMFActivate *activate = NULL;
+ if (mediaType == Audio) {
+ activate = m_playerService->audioEndpointControl()->createActivate();
+ } else if (mediaType == Video) {
+ if (m_playerService->videoRendererControl()) {
+ activate = m_playerService->videoRendererControl()->createActivate();
+ } else if (m_playerService->videoWindowControl()) {
+ activate = m_playerService->videoWindowControl()->createActivate();
+ } else {
+ qWarning() << "no videoWindowControl or videoRendererControl, unable to add output node for video data";
+ }
+ } else {
+ // Unknown stream type.
+ emit error(QMediaPlayer::FormatError, tr("Unknown stream type."), false);
+ }
+
+ if (!activate
+ || FAILED(node->SetObject(activate))
+ || FAILED(node->SetUINT32(MF_TOPONODE_STREAMID, sinkID))
+ || FAILED(node->SetUINT32(MF_TOPONODE_NOSHUTDOWN_ON_REMOVE, FALSE))
+ || FAILED(topology->AddNode(node))) {
+ node->Release();
+ node = NULL;
+ }
+
+ return node;
+}
+
+bool MFPlayerSession::addAudioSampleGrabberNode(IMFTopology *topology)
+{
+ HRESULT hr = S_OK;
+ IMFMediaType *pType = 0;
+ IMFActivate *sinkActivate = 0;
+ do {
+ hr = MFCreateMediaType(&pType);
+ if (FAILED(hr))
+ break;
+
+ hr = pType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio);
+ if (FAILED(hr))
+ break;
+
+ hr = pType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM);
+ if (FAILED(hr))
+ break;
+
+ hr = MFCreateSampleGrabberSinkActivate(pType, m_audioSampleGrabber, &sinkActivate);
+ if (FAILED(hr))
+ break;
+
+ // Note: Data is distorted if this attribute is enabled
+ hr = sinkActivate->SetUINT32(MF_SAMPLEGRABBERSINK_IGNORE_CLOCK, FALSE);
+ if (FAILED(hr))
+ break;
+
+ hr = MFCreateTopologyNode(MF_TOPOLOGY_OUTPUT_NODE, &m_audioSampleGrabberNode);
+ if (FAILED(hr))
+ break;
+
+ hr = m_audioSampleGrabberNode->SetObject(sinkActivate);
+ if (FAILED(hr))
+ break;
+
+ hr = m_audioSampleGrabberNode->SetUINT32(MF_TOPONODE_STREAMID, 0); // Identifier of the stream sink.
+ if (FAILED(hr))
+ break;
+
+ hr = m_audioSampleGrabberNode->SetUINT32(MF_TOPONODE_NOSHUTDOWN_ON_REMOVE, FALSE);
+ if (FAILED(hr))
+ break;
+
+ hr = topology->AddNode(m_audioSampleGrabberNode);
+ if (FAILED(hr))
+ break;
+
+ pType->Release();
+ sinkActivate->Release();
+ return true;
+ } while (false);
+
+ if (pType)
+ pType->Release();
+ if (sinkActivate)
+ sinkActivate->Release();
+ if (m_audioSampleGrabberNode) {
+ m_audioSampleGrabberNode->Release();
+ m_audioSampleGrabberNode = NULL;
+ }
+ return false;
+}
+
+bool MFPlayerSession::setupAudioSampleGrabber(IMFTopology *topology, IMFTopologyNode *sourceNode, IMFTopologyNode *outputNode)
+{
+ if (!addAudioSampleGrabberNode(topology))
+ return false;
+
+ HRESULT hr = S_OK;
+ IMFTopologyNode *pTeeNode = NULL;
+
+ IMFMediaTypeHandler *typeHandler = NULL;
+ IMFMediaType *mediaType = NULL;
+ do {
+ hr = MFCreateTopologyNode(MF_TOPOLOGY_TEE_NODE, &pTeeNode);
+ if (FAILED(hr))
+ break;
+ hr = sourceNode->ConnectOutput(0, pTeeNode, 0);
+ if (FAILED(hr))
+ break;
+ hr = pTeeNode->ConnectOutput(0, outputNode, 0);
+ if (FAILED(hr))
+ break;
+ hr = pTeeNode->ConnectOutput(1, m_audioSampleGrabberNode, 0);
+ if (FAILED(hr))
+ break;
+ } while (false);
+
+ if (pTeeNode)
+ pTeeNode->Release();
+ if (mediaType)
+ mediaType->Release();
+ if (typeHandler)
+ typeHandler->Release();
+ return hr == S_OK;
+}
+
+QAudioFormat MFPlayerSession::audioFormatForMFMediaType(IMFMediaType *mediaType) const
+{
+ WAVEFORMATEX *wfx = 0;
+ UINT32 size;
+ HRESULT hr = MFCreateWaveFormatExFromMFMediaType(mediaType, &wfx, &size, MFWaveFormatExConvertFlag_Normal);
+ if (FAILED(hr))
+ return QAudioFormat();
+
+ if (size < sizeof(WAVEFORMATEX)) {
+ CoTaskMemFree(wfx);
+ return QAudioFormat();
+ }
+
+ if (wfx->wFormatTag != WAVE_FORMAT_PCM) {
+ CoTaskMemFree(wfx);
+ return QAudioFormat();
+ }
+
+ QAudioFormat format;
+ format.setSampleRate(wfx->nSamplesPerSec);
+ format.setChannelCount(wfx->nChannels);
+ format.setSampleSize(wfx->wBitsPerSample);
+ format.setCodec("audio/x-raw");
+ format.setByteOrder(QAudioFormat::LittleEndian);
+ if (format.sampleSize() == 8)
+ format.setSampleType(QAudioFormat::UnSignedInt);
+ else
+ format.setSampleType(QAudioFormat::SignedInt);
+
+ CoTaskMemFree(wfx);
+ return format;
+}
+
+// BindOutputNode
+// Sets the IMFStreamSink pointer on an output node.
+// IMFActivate pointer in the output node must be converted to an
+// IMFStreamSink pointer before the topology loader resolves the topology.
+HRESULT BindOutputNode(IMFTopologyNode *pNode)
+{
+ IUnknown *nodeObject = NULL;
+ IMFActivate *activate = NULL;
+ IMFStreamSink *stream = NULL;
+ IMFMediaSink *sink = NULL;
+
+ HRESULT hr = pNode->GetObject(&nodeObject);
+ if (FAILED(hr))
+ return hr;
+
+ hr = nodeObject->QueryInterface(IID_PPV_ARGS(&activate));
+ if (SUCCEEDED(hr)) {
+ DWORD dwStreamID = 0;
+
+ // Try to create the media sink.
+ hr = activate->ActivateObject(IID_PPV_ARGS(&sink));
+ if (SUCCEEDED(hr))
+ dwStreamID = MFGetAttributeUINT32(pNode, MF_TOPONODE_STREAMID, 0);
+
+ if (SUCCEEDED(hr)) {
+ // First check if the media sink already has a stream sink with the requested ID.
+ hr = sink->GetStreamSinkById(dwStreamID, &stream);
+ if (FAILED(hr)) {
+ // Create the stream sink.
+ hr = sink->AddStreamSink(dwStreamID, NULL, &stream);
+ }
+ }
+
+ // Replace the node's object pointer with the stream sink.
+ if (SUCCEEDED(hr)) {
+ hr = pNode->SetObject(stream);
+ }
+ } else {
+ hr = nodeObject->QueryInterface(IID_PPV_ARGS(&stream));
+ }
+
+ if (nodeObject)
+ nodeObject->Release();
+ if (activate)
+ activate->Release();
+ if (stream)
+ stream->Release();
+ if (sink)
+ sink->Release();
+ return hr;
+}
+
+// BindOutputNodes
+// Sets the IMFStreamSink pointers on all of the output nodes in a topology.
+HRESULT BindOutputNodes(IMFTopology *pTopology)
+{
+ IMFCollection *collection;
+
+ // Get the collection of output nodes.
+ HRESULT hr = pTopology->GetOutputNodeCollection(&collection);
+
+ // Enumerate all of the nodes in the collection.
+ if (SUCCEEDED(hr)) {
+ DWORD cNodes;
+ hr = collection->GetElementCount(&cNodes);
+
+ if (SUCCEEDED(hr)) {
+ for (DWORD i = 0; i < cNodes; i++) {
+ IUnknown *element;
+ hr = collection->GetElement(i, &element);
+ if (FAILED(hr))
+ break;
+
+ IMFTopologyNode *node;
+ hr = element->QueryInterface(IID_IMFTopologyNode, (void**)&node);
+ element->Release();
+ if (FAILED(hr))
+ break;
+
+ // Bind this node.
+ hr = BindOutputNode(node);
+ node->Release();
+ if (FAILED(hr))
+ break;
+ }
+ }
+ collection->Release();
+ }
+
+ return hr;
+}
+
+// This method binds output nodes to complete the topology,
+// then loads the topology and inserts MFT between the output node
+// and a filter connected to the output node.
+IMFTopology *MFPlayerSession::insertMFT(IMFTopology *topology, TOPOID outputNodeId)
+{
+ bool isNewTopology = false;
+
+ IMFTopoLoader *topoLoader = 0;
+ IMFTopology *resolvedTopology = 0;
+ IMFCollection *outputNodes = 0;
+
+ do {
+ if (FAILED(BindOutputNodes(topology)))
+ break;
+
+ if (FAILED(MFCreateTopoLoader(&topoLoader)))
+ break;
+
+ if (FAILED(topoLoader->Load(topology, &resolvedTopology, NULL))) {
+ // Topology could not be resolved, adding ourselves a color converter
+ // to the topology might solve the problem
+ insertColorConverter(topology, outputNodeId);
+ if (FAILED(topoLoader->Load(topology, &resolvedTopology, NULL)))
+ break;
+ }
+
+ if (insertResizer(resolvedTopology))
+ isNewTopology = true;
+
+ // Get all output nodes and search for video output node.
+ if (FAILED(resolvedTopology->GetOutputNodeCollection(&outputNodes)))
+ break;
+
+ DWORD elementCount = 0;
+ if (FAILED(outputNodes->GetElementCount(&elementCount)))
+ break;
+
+ for (DWORD n = 0; n < elementCount; n++) {
+ IUnknown *element = 0;
+ IMFTopologyNode *node = 0;
+ IUnknown *outputObject = 0;
+ IMFTopologyNode *inputNode = 0;
+ IMFTopologyNode *mftNode = 0;
+ bool mftAdded = false;
+
+ do {
+ if (FAILED(outputNodes->GetElement(n, &element)))
+ break;
+
+ if (FAILED(element->QueryInterface(IID_IMFTopologyNode, (void**)&node)))
+ break;
+
+ TOPOID id;
+ if (FAILED(node->GetTopoNodeID(&id)))
+ break;
+
+ if (id != outputNodeId)
+ break;
+
+ if (FAILED(node->GetObject(&outputObject)))
+ break;
+
+ m_videoProbeMFT->setVideoSink(outputObject);
+
+ // Insert MFT between the output node and the node connected to it.
+ DWORD outputIndex = 0;
+ if (FAILED(node->GetInput(0, &inputNode, &outputIndex)))
+ break;
+
+ if (FAILED(MFCreateTopologyNode(MF_TOPOLOGY_TRANSFORM_NODE, &mftNode)))
+ break;
+
+ if (FAILED(mftNode->SetObject(m_videoProbeMFT)))
+ break;
+
+ if (FAILED(resolvedTopology->AddNode(mftNode)))
+ break;
+
+ if (FAILED(inputNode->ConnectOutput(0, mftNode, 0)))
+ break;
+
+ if (FAILED(mftNode->ConnectOutput(0, node, 0)))
+ break;
+
+ mftAdded = true;
+ isNewTopology = true;
+ } while (false);
+
+ if (mftNode)
+ mftNode->Release();
+ if (inputNode)
+ inputNode->Release();
+ if (node)
+ node->Release();
+ if (element)
+ element->Release();
+ if (outputObject)
+ outputObject->Release();
+
+ if (mftAdded)
+ break;
+ else
+ m_videoProbeMFT->setVideoSink(NULL);
+ }
+ } while (false);
+
+ if (outputNodes)
+ outputNodes->Release();
+
+ if (topoLoader)
+ topoLoader->Release();
+
+ if (isNewTopology) {
+ topology->Release();
+ return resolvedTopology;
+ }
+
+ if (resolvedTopology)
+ resolvedTopology->Release();
+
+ return topology;
+}
+
+// This method checks if the topology contains a color converter transform (CColorConvertDMO),
+// if it does it inserts a resizer transform (CResizerDMO) to handle dynamic frame size change
+// of the video stream.
+// Returns true if it inserted a resizer
+bool MFPlayerSession::insertResizer(IMFTopology *topology)
+{
+ bool inserted = false;
+ WORD elementCount = 0;
+ IMFTopologyNode *node = 0;
+ IUnknown *object = 0;
+ IWMColorConvProps *colorConv = 0;
+ IMFTransform *resizer = 0;
+ IMFTopologyNode *resizerNode = 0;
+ IMFTopologyNode *inputNode = 0;
+
+ HRESULT hr = topology->GetNodeCount(&elementCount);
+ if (FAILED(hr))
+ return false;
+
+ for (WORD i = 0; i < elementCount; ++i) {
+ if (node) {
+ node->Release();
+ node = 0;
+ }
+ if (object) {
+ object->Release();
+ object = 0;
+ }
+
+ if (FAILED(topology->GetNode(i, &node)))
+ break;
+
+ MF_TOPOLOGY_TYPE nodeType;
+ if (FAILED(node->GetNodeType(&nodeType)))
+ break;
+
+ if (nodeType != MF_TOPOLOGY_TRANSFORM_NODE)
+ continue;
+
+ if (FAILED(node->GetObject(&object)))
+ break;
+
+ if (FAILED(object->QueryInterface(&colorConv)))
+ continue;
+
+ if (FAILED(CoCreateInstance(CLSID_CResizerDMO, NULL, CLSCTX_INPROC_SERVER, IID_IMFTransform, (void**)&resizer)))
+ break;
+
+ if (FAILED(MFCreateTopologyNode(MF_TOPOLOGY_TRANSFORM_NODE, &resizerNode)))
+ break;
+
+ if (FAILED(resizerNode->SetObject(resizer)))
+ break;
+
+ if (FAILED(topology->AddNode(resizerNode)))
+ break;
+
+ DWORD outputIndex = 0;
+ if (FAILED(node->GetInput(0, &inputNode, &outputIndex))) {
+ topology->RemoveNode(resizerNode);
+ break;
+ }
+
+ if (FAILED(inputNode->ConnectOutput(0, resizerNode, 0))) {
+ topology->RemoveNode(resizerNode);
+ break;
+ }
+
+ if (FAILED(resizerNode->ConnectOutput(0, node, 0))) {
+ inputNode->ConnectOutput(0, node, 0);
+ topology->RemoveNode(resizerNode);
+ break;
+ }
+
+ inserted = true;
+ break;
+ }
+
+ if (node)
+ node->Release();
+ if (object)
+ object->Release();
+ if (colorConv)
+ colorConv->Release();
+ if (resizer)
+ resizer->Release();
+ if (resizerNode)
+ resizerNode->Release();
+ if (inputNode)
+ inputNode->Release();
+
+ return inserted;
+}
+
+// This method inserts a color converter (CColorConvertDMO) in the topology,
+// typically to convert to RGB format.
+// Usually this converter is automatically inserted when the topology is resolved but
+// for some reason it fails to do so in some cases, we then do it ourselves.
+void MFPlayerSession::insertColorConverter(IMFTopology *topology, TOPOID outputNodeId)
+{
+ IMFCollection *outputNodes = 0;
+
+ if (FAILED(topology->GetOutputNodeCollection(&outputNodes)))
+ return;
+
+ DWORD elementCount = 0;
+ if (FAILED(outputNodes->GetElementCount(&elementCount)))
+ goto done;
+
+ for (DWORD n = 0; n < elementCount; n++) {
+ IUnknown *element = 0;
+ IMFTopologyNode *node = 0;
+ IMFTopologyNode *inputNode = 0;
+ IMFTopologyNode *mftNode = 0;
+ IMFTransform *converter = 0;
+
+ do {
+ if (FAILED(outputNodes->GetElement(n, &element)))
+ break;
+
+ if (FAILED(element->QueryInterface(IID_IMFTopologyNode, (void**)&node)))
+ break;
+
+ TOPOID id;
+ if (FAILED(node->GetTopoNodeID(&id)))
+ break;
+
+ if (id != outputNodeId)
+ break;
+
+ DWORD outputIndex = 0;
+ if (FAILED(node->GetInput(0, &inputNode, &outputIndex)))
+ break;
+
+ if (FAILED(MFCreateTopologyNode(MF_TOPOLOGY_TRANSFORM_NODE, &mftNode)))
+ break;
+
+ if (FAILED(CoCreateInstance(CLSID_CColorConvertDMO, NULL, CLSCTX_INPROC_SERVER, IID_IMFTransform, (void**)&converter)))
+ break;
+
+ if (FAILED(mftNode->SetObject(converter)))
+ break;
+
+ if (FAILED(topology->AddNode(mftNode)))
+ break;
+
+ if (FAILED(inputNode->ConnectOutput(0, mftNode, 0)))
+ break;
+
+ if (FAILED(mftNode->ConnectOutput(0, node, 0)))
+ break;
+
+ } while (false);
+
+ if (mftNode)
+ mftNode->Release();
+ if (inputNode)
+ inputNode->Release();
+ if (node)
+ node->Release();
+ if (element)
+ element->Release();
+ if (converter)
+ converter->Release();
+ }
+
+done:
+ if (outputNodes)
+ outputNodes->Release();
+}
+
+void MFPlayerSession::stop(bool immediate)
+{
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "stop";
+#endif
+ if (!immediate && m_pendingState != NoPending) {
+ m_request.setCommand(CmdStop);
+ } else {
+ if (m_state.command == CmdStop)
+ return;
+
+ if (m_scrubbing)
+ scrub(false);
+
+ if (SUCCEEDED(m_session->Stop())) {
+ m_state.setCommand(CmdStop);
+ m_pendingState = CmdPending;
+ if (m_status != QMediaPlayer::EndOfMedia) {
+ m_varStart.vt = VT_I8;
+ m_varStart.hVal.QuadPart = 0;
+ }
+ } else {
+ emit error(QMediaPlayer::ResourceError, tr("Failed to stop."), true);
+ }
+ }
+}
+
+void MFPlayerSession::start()
+{
+ if (m_status == QMediaPlayer::EndOfMedia)
+ m_varStart.hVal.QuadPart = 0; // restart from the beginning
+
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "start";
+#endif
+
+ if (m_pendingState != NoPending) {
+ m_request.setCommand(CmdStart);
+ } else {
+ if (m_state.command == CmdStart)
+ return;
+
+ if (m_scrubbing)
+ scrub(false);
+
+ if (SUCCEEDED(m_session->Start(&GUID_NULL, &m_varStart))) {
+ m_state.setCommand(CmdStart);
+ m_pendingState = CmdPending;
+ PropVariantInit(&m_varStart);
+ } else {
+ emit error(QMediaPlayer::ResourceError, tr("failed to start playback"), true);
+ }
+ }
+}
+
+void MFPlayerSession::pause()
+{
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "pause";
+#endif
+ if (m_pendingState != NoPending) {
+ m_request.setCommand(CmdPause);
+ } else {
+ if (m_state.command == CmdPause)
+ return;
+
+ if (SUCCEEDED(m_session->Pause())) {
+ m_state.setCommand(CmdPause);
+ m_pendingState = CmdPending;
+ } else {
+ emit error(QMediaPlayer::ResourceError, tr("Failed to pause."), false);
+ }
+ }
+}
+
+void MFPlayerSession::changeStatus(QMediaPlayer::MediaStatus newStatus)
+{
+ if (m_status == newStatus)
+ return;
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "MFPlayerSession::changeStatus" << newStatus;
+#endif
+ m_status = newStatus;
+ emit statusChanged();
+}
+
+QMediaPlayer::MediaStatus MFPlayerSession::status() const
+{
+ return m_status;
+}
+
+void MFPlayerSession::createSession()
+{
+ close();
+
+ m_hCloseEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+
+ m_sourceResolver = new SourceResolver();
+ QObject::connect(m_sourceResolver, SIGNAL(mediaSourceReady()), this, SLOT(handleMediaSourceReady()));
+ QObject::connect(m_sourceResolver, SIGNAL(error(long)), this, SLOT(handleSourceError(long)));
+
+ m_videoProbeMFT = new MFTransform;
+ for (int i = 0; i < m_videoProbes.size(); ++i)
+ m_videoProbeMFT->addProbe(m_videoProbes.at(i));
+
+ Q_ASSERT(m_session == NULL);
+ HRESULT hr = MFCreateMediaSession(NULL, &m_session);
+ if (FAILED(hr)) {
+ changeStatus(QMediaPlayer::UnknownMediaStatus);
+ emit error(QMediaPlayer::ResourceError, tr("Unable to create mediasession."), true);
+ }
+
+ hr = m_session->BeginGetEvent(this, m_session);
+
+ if (FAILED(hr)) {
+ changeStatus(QMediaPlayer::UnknownMediaStatus);
+ emit error(QMediaPlayer::ResourceError, tr("Unable to pull session events."), false);
+ }
+
+ PropVariantInit(&m_varStart);
+ m_varStart.vt = VT_I8;
+ m_varStart.hVal.QuadPart = 0;
+}
+
+qint64 MFPlayerSession::position()
+{
+ if (m_request.command == CmdSeek || m_request.command == CmdSeekResume)
+ return m_request.start;
+
+ if (m_pendingState == SeekPending)
+ return m_state.start;
+
+ if (m_state.command == CmdStop)
+ return qint64(m_varStart.hVal.QuadPart / 10000);
+
+ if (m_presentationClock) {
+ MFTIME time, sysTime;
+ if (FAILED(m_presentationClock->GetCorrelatedTime(0, &time, &sysTime)))
+ return 0;
+ return qint64(time / 10000);
+ }
+ return 0;
+}
+
+void MFPlayerSession::setPosition(qint64 position)
+{
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "setPosition";
+#endif
+ if (m_pendingState != NoPending) {
+ m_request.setCommand(CmdSeek);
+ m_request.start = position;
+ } else {
+ setPositionInternal(position, CmdNone);
+ }
+}
+
+void MFPlayerSession::setPositionInternal(qint64 position, Command requestCmd)
+{
+ if (m_status == QMediaPlayer::EndOfMedia)
+ changeStatus(QMediaPlayer::LoadedMedia);
+ if (m_state.command == CmdStop && requestCmd != CmdSeekResume) {
+ m_varStart.vt = VT_I8;
+ m_varStart.hVal.QuadPart = LONGLONG(position * 10000);
+ // Even though the position is not actually set on the session yet,
+ // report it to have changed anyway for UI controls to be updated
+ emit positionChanged(this->position());
+ return;
+ }
+
+ if (m_state.command == CmdPause)
+ scrub(true);
+
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "setPositionInternal";
+#endif
+
+ PROPVARIANT varStart;
+ varStart.vt = VT_I8;
+ varStart.hVal.QuadPart = LONGLONG(position * 10000);
+ if (SUCCEEDED(m_session->Start(NULL, &varStart)))
+ {
+ PropVariantInit(&m_varStart);
+ // Store the pending state.
+ m_state.setCommand(CmdStart);
+ m_state.start = position;
+ m_pendingState = SeekPending;
+ } else {
+ emit error(QMediaPlayer::ResourceError, tr("Failed to seek."), true);
+ }
+}
+
+qreal MFPlayerSession::playbackRate() const
+{
+ if (m_scrubbing)
+ return m_restoreRate;
+ return m_state.rate;
+}
+
+void MFPlayerSession::setPlaybackRate(qreal rate)
+{
+ if (m_scrubbing) {
+ m_restoreRate = rate;
+ emit playbackRateChanged(rate);
+ return;
+ }
+ setPlaybackRateInternal(rate);
+}
+
+void MFPlayerSession::setPlaybackRateInternal(qreal rate)
+{
+ if (rate == m_request.rate)
+ return;
+
+ m_pendingRate = rate;
+ if (!m_rateSupport)
+ return;
+
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "setPlaybackRate";
+#endif
+ BOOL isThin = FALSE;
+
+ //from MSDN http://msdn.microsoft.com/en-us/library/aa965220%28v=vs.85%29.aspx
+ //Thinning applies primarily to video streams.
+ //In thinned mode, the source drops delta frames and deliver only key frames.
+ //At very high playback rates, the source might skip some key frames (for example, deliver every other key frame).
+
+ if (FAILED(m_rateSupport->IsRateSupported(FALSE, rate, NULL))) {
+ isThin = TRUE;
+ if (FAILED(m_rateSupport->IsRateSupported(isThin, rate, NULL))) {
+ qWarning() << "unable to set playbackrate = " << rate;
+ m_pendingRate = m_request.rate = m_state.rate;
+ return;
+ }
+ }
+ if (m_pendingState != NoPending) {
+ m_request.rate = rate;
+ m_request.isThin = isThin;
+ // Remember the current transport state (play, paused, etc), so that we
+ // can restore it after the rate change, if necessary. However, if
+ // anothercommand is already pending, that one takes precedent.
+ if (m_request.command == CmdNone)
+ m_request.setCommand(m_state.command);
+ } else {
+ //No pending operation. Commit the new rate.
+ commitRateChange(rate, isThin);
+ }
+}
+
+void MFPlayerSession::commitRateChange(qreal rate, BOOL isThin)
+{
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "commitRateChange";
+#endif
+ Q_ASSERT(m_pendingState == NoPending);
+ MFTIME hnsSystemTime = 0;
+ MFTIME hnsClockTime = 0;
+ Command cmdNow = m_state.command;
+ bool resetPosition = false;
+ // Allowed rate transitions:
+ // Positive <-> negative: Stopped
+ // Negative <-> zero: Stopped
+ // Postive <-> zero: Paused or stopped
+ if ((rate > 0 && m_state.rate <= 0) || (rate < 0 && m_state.rate >= 0)) {
+ if (cmdNow == CmdStart) {
+ // Get the current clock position. This will be the restart time.
+ m_presentationClock->GetCorrelatedTime(0, &hnsClockTime, &hnsSystemTime);
+ Q_ASSERT(hnsSystemTime != 0);
+
+ if (rate < 0 || m_state.rate < 0)
+ m_request.setCommand(CmdSeekResume);
+ else if (isThin || m_state.isThin)
+ m_request.setCommand(CmdStartAndSeek);
+ else
+ m_request.setCommand(CmdStart);
+
+ // We need to stop only when dealing with negative rates
+ if (rate >= 0 && m_state.rate >= 0)
+ pause();
+ else
+ stop();
+
+ // If we deal with negative rates, we stopped the session and consequently
+ // reset the position to zero. We then need to resume to the current position.
+ m_request.start = hnsClockTime / 10000;
+ } else if (cmdNow == CmdPause) {
+ if (rate < 0 || m_state.rate < 0) {
+ // The current state is paused.
+ // For this rate change, the session must be stopped. However, the
+ // session cannot transition back from stopped to paused.
+ // Therefore, this rate transition is not supported while paused.
+ qWarning() << "Unable to change rate from positive to negative or vice versa in paused state";
+ rate = m_state.rate;
+ isThin = m_state.isThin;
+ goto done;
+ }
+
+ // This happens when resuming playback after scrubbing in pause mode.
+ // This transition requires the session to be paused. Even though our
+ // internal state is set to paused, the session might not be so we need
+ // to enforce it
+ if (rate > 0 && m_state.rate == 0) {
+ m_state.setCommand(CmdNone);
+ pause();
+ }
+ }
+ } else if (rate == 0 && m_state.rate > 0) {
+ if (cmdNow != CmdPause) {
+ // Transition to paused.
+ // This transisition requires the paused state.
+ // Pause and set the rate.
+ pause();
+
+ // Request: Switch back to current state.
+ m_request.setCommand(cmdNow);
+ }
+ } else if (rate == 0 && m_state.rate < 0) {
+ // Changing rate from negative to zero requires to stop the session
+ m_presentationClock->GetCorrelatedTime(0, &hnsClockTime, &hnsSystemTime);
+
+ m_request.setCommand(CmdSeekResume);
+
+ stop();
+
+ // Resume to the current position (stop() will reset the position to 0)
+ m_request.start = hnsClockTime / 10000;
+ } else if (!isThin && m_state.isThin) {
+ if (cmdNow == CmdStart) {
+ // When thinning, only key frames are read and presented. Going back
+ // to normal playback requires to reset the current position to force
+ // the pipeline to decode the actual frame at the current position
+ // (which might be earlier than the last decoded key frame)
+ resetPosition = true;
+ } else if (cmdNow == CmdPause) {
+ // If paused, don't reset the position until we resume, otherwise
+ // a new frame will be rendered
+ m_presentationClock->GetCorrelatedTime(0, &hnsClockTime, &hnsSystemTime);
+ m_request.setCommand(CmdSeekResume);
+ m_request.start = hnsClockTime / 10000;
+ }
+
+ }
+
+ // Set the rate.
+ if (FAILED(m_rateControl->SetRate(isThin, rate))) {
+ qWarning() << "failed to set playbackrate = " << rate;
+ rate = m_state.rate;
+ isThin = m_state.isThin;
+ goto done;
+ }
+
+ if (resetPosition) {
+ m_presentationClock->GetCorrelatedTime(0, &hnsClockTime, &hnsSystemTime);
+ setPosition(hnsClockTime / 10000);
+ }
+
+done:
+ // Adjust our current rate and requested rate.
+ m_pendingRate = m_request.rate = m_state.rate = rate;
+ if (rate != 0)
+ m_state.isThin = isThin;
+ emit playbackRateChanged(rate);
+}
+
+void MFPlayerSession::scrub(bool enableScrub)
+{
+ if (m_scrubbing == enableScrub)
+ return;
+
+ m_scrubbing = enableScrub;
+
+ if (!canScrub()) {
+ if (!enableScrub)
+ m_pendingRate = m_restoreRate;
+ return;
+ }
+
+ if (enableScrub) {
+ // Enter scrubbing mode. Cache the rate.
+ m_restoreRate = m_request.rate;
+ setPlaybackRateInternal(0.0f);
+ } else {
+ // Leaving scrubbing mode. Restore the old rate.
+ setPlaybackRateInternal(m_restoreRate);
+ }
+}
+
+int MFPlayerSession::volume() const
+{
+ return m_volume;
+}
+
+void MFPlayerSession::setVolume(int volume)
+{
+ if (m_volume == volume)
+ return;
+ m_volume = volume;
+
+ if (!m_muted)
+ setVolumeInternal(volume);
+
+ emit volumeChanged(m_volume);
+}
+
+bool MFPlayerSession::isMuted() const
+{
+ return m_muted;
+}
+
+void MFPlayerSession::setMuted(bool muted)
+{
+ if (m_muted == muted)
+ return;
+ m_muted = muted;
+
+ setVolumeInternal(muted ? 0 : m_volume);
+
+ emit mutedChanged(m_muted);
+}
+
+void MFPlayerSession::setVolumeInternal(int volume)
+{
+ if (m_volumeControl) {
+ quint32 channelCount = 0;
+ if (!SUCCEEDED(m_volumeControl->GetChannelCount(&channelCount))
+ || channelCount == 0)
+ return;
+
+ float scaled = volume * 0.01f;
+ for (quint32 i = 0; i < channelCount; ++i)
+ m_volumeControl->SetChannelVolume(i, scaled);
+ }
+}
+
+int MFPlayerSession::bufferStatus()
+{
+ if (!m_netsourceStatistics)
+ return 0;
+ PROPVARIANT var;
+ PropVariantInit(&var);
+ PROPERTYKEY key;
+ key.fmtid = MFNETSOURCE_STATISTICS;
+ key.pid = MFNETSOURCE_BUFFERPROGRESS_ID;
+ int progress = -1;
+ // GetValue returns S_FALSE if the property is not available, which has
+ // a value > 0. We therefore can't use the SUCCEEDED macro here.
+ if (m_netsourceStatistics->GetValue(key, &var) == S_OK) {
+ progress = var.lVal;
+ PropVariantClear(&var);
+ }
+
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "bufferStatus: progress = " << progress;
+#endif
+
+ return progress;
+}
+
+QMediaTimeRange MFPlayerSession::availablePlaybackRanges()
+{
+ // defaults to the whole media
+ qint64 start = 0;
+ qint64 end = qint64(m_duration / 10000);
+
+ if (m_netsourceStatistics) {
+ PROPVARIANT var;
+ PropVariantInit(&var);
+ PROPERTYKEY key;
+ key.fmtid = MFNETSOURCE_STATISTICS;
+ key.pid = MFNETSOURCE_SEEKRANGESTART_ID;
+ // GetValue returns S_FALSE if the property is not available, which has
+ // a value > 0. We therefore can't use the SUCCEEDED macro here.
+ if (m_netsourceStatistics->GetValue(key, &var) == S_OK) {
+ start = qint64(var.uhVal.QuadPart / 10000);
+ PropVariantClear(&var);
+ PropVariantInit(&var);
+ key.pid = MFNETSOURCE_SEEKRANGEEND_ID;
+ if (m_netsourceStatistics->GetValue(key, &var) == S_OK) {
+ end = qint64(var.uhVal.QuadPart / 10000);
+ PropVariantClear(&var);
+ }
+ }
+ }
+
+ return QMediaTimeRange(start, end);
+}
+
+HRESULT MFPlayerSession::QueryInterface(REFIID riid, void** ppvObject)
+{
+ if (!ppvObject)
+ return E_POINTER;
+ if (riid == IID_IMFAsyncCallback) {
+ *ppvObject = static_cast<IMFAsyncCallback*>(this);
+ } else if (riid == IID_IUnknown) {
+ *ppvObject = static_cast<IUnknown*>(this);
+ } else {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+ return S_OK;
+}
+
+ULONG MFPlayerSession::AddRef(void)
+{
+ return InterlockedIncrement(&m_cRef);
+}
+
+ULONG MFPlayerSession::Release(void)
+{
+ LONG cRef = InterlockedDecrement(&m_cRef);
+ if (cRef == 0)
+ this->deleteLater();
+ return cRef;
+}
+
+HRESULT MFPlayerSession::Invoke(IMFAsyncResult *pResult)
+{
+ if (pResult->GetStateNoAddRef() != m_session)
+ return S_OK;
+
+ IMFMediaEvent *pEvent = NULL;
+ // Get the event from the event queue.
+ HRESULT hr = m_session->EndGetEvent(pResult, &pEvent);
+ if (FAILED(hr)) {
+ return S_OK;
+ }
+
+ MediaEventType meType = MEUnknown;
+ hr = pEvent->GetType(&meType);
+ if (FAILED(hr)) {
+ pEvent->Release();
+ return S_OK;
+ }
+
+ if (meType == MESessionClosed) {
+ SetEvent(m_hCloseEvent);
+ pEvent->Release();
+ return S_OK;
+ } else {
+ hr = m_session->BeginGetEvent(this, m_session);
+ if (FAILED(hr)) {
+ pEvent->Release();
+ return S_OK;
+ }
+ }
+
+ if (!m_closing) {
+ emit sessionEvent(pEvent);
+ } else {
+ pEvent->Release();
+ }
+ return S_OK;
+}
+
+void MFPlayerSession::handleSessionEvent(IMFMediaEvent *sessionEvent)
+{
+ HRESULT hrStatus = S_OK;
+ HRESULT hr = sessionEvent->GetStatus(&hrStatus);
+ if (FAILED(hr) || !m_session) {
+ sessionEvent->Release();
+ return;
+ }
+
+ MediaEventType meType = MEUnknown;
+ hr = sessionEvent->GetType(&meType);
+
+#ifdef DEBUG_MEDIAFOUNDATION
+ if (FAILED(hrStatus))
+ qDebug() << "handleSessionEvent: MediaEventType = " << meType << "Failed";
+ else
+ qDebug() << "handleSessionEvent: MediaEventType = " << meType;
+#endif
+
+ switch (meType) {
+ case MENonFatalError: {
+ PROPVARIANT var;
+ PropVariantInit(&var);
+ sessionEvent->GetValue(&var);
+ qWarning() << "handleSessionEvent: non fatal error = " << var.ulVal;
+ PropVariantClear(&var);
+ emit error(QMediaPlayer::ResourceError, tr("Media session non-fatal error."), false);
+ }
+ break;
+ case MESourceUnknown:
+ changeStatus(QMediaPlayer::InvalidMedia);
+ break;
+ case MEError:
+ changeStatus(QMediaPlayer::UnknownMediaStatus);
+ qWarning() << "handleSessionEvent: serious error = " << hrStatus;
+ emit error(QMediaPlayer::ResourceError, tr("Media session serious error."), true);
+ break;
+ case MESessionRateChanged:
+ // If the rate change succeeded, we've already got the rate
+ // cached. If it failed, try to get the actual rate.
+ if (FAILED(hrStatus)) {
+ PROPVARIANT var;
+ PropVariantInit(&var);
+ if (SUCCEEDED(sessionEvent->GetValue(&var)) && (var.vt == VT_R4)) {
+ m_state.rate = var.fltVal;
+ }
+ emit playbackRateChanged(playbackRate());
+ }
+ break;
+ case MESessionScrubSampleComplete :
+ if (m_scrubbing)
+ updatePendingCommands(CmdStart);
+ break;
+ case MESessionStarted:
+ if (m_status == QMediaPlayer::EndOfMedia
+ || m_status == QMediaPlayer::LoadedMedia) {
+ // If the session started, then enough data is buffered to play
+ changeStatus(QMediaPlayer::BufferedMedia);
+ }
+
+ updatePendingCommands(CmdStart);
+ // playback started, we can now set again the procAmpValues if they have been
+ // changed previously (these are lost when loading a new media)
+ if (m_playerService->videoWindowControl()) {
+ m_playerService->videoWindowControl()->applyImageControls();
+ }
+ break;
+ case MESessionStopped:
+ if (m_status != QMediaPlayer::EndOfMedia) {
+ m_varStart.vt = VT_I8;
+ m_varStart.hVal.QuadPart = 0;
+
+ // Reset to Loaded status unless we are loading a new media
+ // or changing the playback rate to negative values (stop required)
+ if (m_status != QMediaPlayer::LoadingMedia && m_request.command != CmdSeekResume)
+ changeStatus(QMediaPlayer::LoadedMedia);
+ }
+ updatePendingCommands(CmdStop);
+ break;
+ case MESessionPaused:
+ updatePendingCommands(CmdPause);
+ break;
+ case MEReconnectStart:
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "MEReconnectStart" << ((hrStatus == S_OK) ? "OK" : "Failed");
+#endif
+ break;
+ case MEReconnectEnd:
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "MEReconnectEnd" << ((hrStatus == S_OK) ? "OK" : "Failed");
+#endif
+ break;
+ case MESessionTopologySet:
+ if (FAILED(hrStatus)) {
+ changeStatus(QMediaPlayer::InvalidMedia);
+ emit error(QMediaPlayer::FormatError, tr("Unsupported media, a codec is missing."), true);
+ } else {
+ if (m_audioSampleGrabberNode) {
+ IUnknown *obj = 0;
+ if (SUCCEEDED(m_audioSampleGrabberNode->GetObject(&obj))) {
+ IMFStreamSink *streamSink = 0;
+ if (SUCCEEDED(obj->QueryInterface(IID_PPV_ARGS(&streamSink)))) {
+ IMFMediaTypeHandler *typeHandler = 0;
+ if (SUCCEEDED(streamSink->GetMediaTypeHandler((&typeHandler)))) {
+ IMFMediaType *mediaType = 0;
+ if (SUCCEEDED(typeHandler->GetCurrentMediaType(&mediaType))) {
+ m_audioSampleGrabber->setFormat(audioFormatForMFMediaType(mediaType));
+ mediaType->Release();
+ }
+ typeHandler->Release();
+ }
+ streamSink->Release();
+ }
+ obj->Release();
+ }
+ }
+
+ // Topology is resolved and successfuly set, this happens only after loading a new media.
+ // Make sure we always start the media from the beginning
+ m_varStart.vt = VT_I8;
+ m_varStart.hVal.QuadPart = 0;
+
+ changeStatus(QMediaPlayer::LoadedMedia);
+ }
+ break;
+ }
+
+ if (FAILED(hrStatus)) {
+ sessionEvent->Release();
+ return;
+ }
+
+ switch (meType) {
+ case MEBufferingStarted:
+ changeStatus(QMediaPlayer::StalledMedia);
+ emit bufferStatusChanged(bufferStatus());
+ break;
+ case MEBufferingStopped:
+ changeStatus(QMediaPlayer::BufferedMedia);
+ emit bufferStatusChanged(bufferStatus());
+ break;
+ case MESessionEnded:
+ m_pendingState = NoPending;
+ m_state.command = CmdStop;
+ m_state.prevCmd = CmdNone;
+ m_request.command = CmdNone;
+ m_request.prevCmd = CmdNone;
+
+ m_varStart.vt = VT_I8;
+ //keep reporting the final position after end of media
+ m_varStart.hVal.QuadPart = m_duration;
+ emit positionChanged(position());
+
+ changeStatus(QMediaPlayer::EndOfMedia);
+ break;
+ case MEEndOfPresentationSegment:
+ break;
+ case MESessionTopologyStatus: {
+ UINT32 status;
+ if (SUCCEEDED(sessionEvent->GetUINT32(MF_EVENT_TOPOLOGY_STATUS, &status))) {
+ if (status == MF_TOPOSTATUS_READY) {
+ IMFClock* clock;
+ if (SUCCEEDED(m_session->GetClock(&clock))) {
+ clock->QueryInterface(IID_IMFPresentationClock, (void**)(&m_presentationClock));
+ clock->Release();
+ }
+
+ if (SUCCEEDED(MFGetService(m_session, MF_RATE_CONTROL_SERVICE, IID_PPV_ARGS(&m_rateControl)))) {
+ if (SUCCEEDED(MFGetService(m_session, MF_RATE_CONTROL_SERVICE, IID_PPV_ARGS(&m_rateSupport)))) {
+ if ((m_mediaTypes & Video) == Video
+ && SUCCEEDED(m_rateSupport->IsRateSupported(TRUE, 0, NULL)))
+ m_canScrub = true;
+ }
+ BOOL isThin = FALSE;
+ float rate = 1;
+ if (SUCCEEDED(m_rateControl->GetRate(&isThin, &rate))) {
+ if (m_pendingRate != rate) {
+ m_state.rate = m_request.rate = rate;
+ setPlaybackRate(m_pendingRate);
+ }
+ }
+ }
+ MFGetService(m_session, MFNETSOURCE_STATISTICS_SERVICE, IID_PPV_ARGS(&m_netsourceStatistics));
+
+ if (SUCCEEDED(MFGetService(m_session, MR_STREAM_VOLUME_SERVICE, IID_PPV_ARGS(&m_volumeControl))))
+ setVolumeInternal(m_muted ? 0 : m_volume);
+ }
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ sessionEvent->Release();
+}
+
+void MFPlayerSession::updatePendingCommands(Command command)
+{
+ emit positionChanged(position());
+ if (m_state.command != command || m_pendingState == NoPending)
+ return;
+
+ // Seek while paused completed
+ if (m_pendingState == SeekPending && m_state.prevCmd == CmdPause) {
+ m_pendingState = NoPending;
+ // A seek operation actually restarts playback. If scrubbing is possible, playback rate
+ // is set to 0.0 at this point and we just need to reset the current state to Pause.
+ // If scrubbing is not possible, the playback rate was not changed and we explicitly need
+ // to re-pause playback.
+ if (!canScrub())
+ pause();
+ else
+ m_state.setCommand(CmdPause);
+ }
+
+ m_pendingState = NoPending;
+
+ //First look for rate changes.
+ if (m_request.rate != m_state.rate) {
+ commitRateChange(m_request.rate, m_request.isThin);
+ }
+
+ // Now look for new requests.
+ if (m_pendingState == NoPending) {
+ switch (m_request.command) {
+ case CmdStart:
+ start();
+ break;
+ case CmdPause:
+ pause();
+ break;
+ case CmdStop:
+ stop();
+ break;
+ case CmdSeek:
+ case CmdSeekResume:
+ setPositionInternal(m_request.start, m_request.command);
+ break;
+ case CmdStartAndSeek:
+ start();
+ setPositionInternal(m_request.start, m_request.command);
+ break;
+ }
+ m_request.setCommand(CmdNone);
+ }
+
+}
+
+bool MFPlayerSession::canScrub() const
+{
+ return m_canScrub && m_rateSupport && m_rateControl;
+}
+
+void MFPlayerSession::clear()
+{
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "MFPlayerSession::clear";
+#endif
+ m_mediaTypes = 0;
+ m_canScrub = false;
+
+ m_pendingState = NoPending;
+ m_state.command = CmdStop;
+ m_state.prevCmd = CmdNone;
+ m_request.command = CmdNone;
+ m_request.prevCmd = CmdNone;
+
+ if (m_presentationClock) {
+ m_presentationClock->Release();
+ m_presentationClock = NULL;
+ }
+ if (m_rateControl) {
+ m_rateControl->Release();
+ m_rateControl = NULL;
+ }
+ if (m_rateSupport) {
+ m_rateSupport->Release();
+ m_rateSupport = NULL;
+ }
+ if (m_volumeControl) {
+ m_volumeControl->Release();
+ m_volumeControl = NULL;
+ }
+ if (m_netsourceStatistics) {
+ m_netsourceStatistics->Release();
+ m_netsourceStatistics = NULL;
+ }
+ if (m_audioSampleGrabberNode) {
+ m_audioSampleGrabberNode->Release();
+ m_audioSampleGrabberNode = NULL;
+ }
+}
diff --git a/src/multimedia/platform/wmf/player/mfplayersession_p.h b/src/multimedia/platform/wmf/player/mfplayersession_p.h
new file mode 100644
index 000000000..92f645017
--- /dev/null
+++ b/src/multimedia/platform/wmf/player/mfplayersession_p.h
@@ -0,0 +1,250 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef MFPLAYERSESSION_H
+#define MFPLAYERSESSION_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <mfapi.h>
+#include <mfidl.h>
+
+#include "qmediaplayer.h"
+#include "qmediaservice.h"
+#include "qmediatimerange.h"
+
+#include <QtCore/qcoreevent.h>
+#include <QtCore/qmutex.h>
+#include <QtCore/qurl.h>
+#include <QtCore/qwaitcondition.h>
+#include <QtMultimedia/qaudioformat.h>
+#include <QtMultimedia/qvideosurfaceformat.h>
+
+QT_BEGIN_NAMESPACE
+class QUrl;
+QT_END_NAMESPACE
+
+QT_USE_NAMESPACE
+
+class SourceResolver;
+class MFAudioEndpointControl;
+class MFVideoRendererControl;
+class MFPlayerControl;
+class MFMetaDataControl;
+class MFPlayerService;
+class AudioSampleGrabberCallback;
+class MFTransform;
+class MFAudioProbeControl;
+class MFVideoProbeControl;
+
+class MFPlayerSession : public QObject, public IMFAsyncCallback
+{
+ Q_OBJECT
+ friend class SourceResolver;
+public:
+ MFPlayerSession(MFPlayerService *playerService = 0);
+ ~MFPlayerSession();
+
+ STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject);
+
+ STDMETHODIMP_(ULONG) AddRef(void);
+
+ STDMETHODIMP_(ULONG) Release(void);
+
+ STDMETHODIMP Invoke(IMFAsyncResult *pResult);
+
+ STDMETHODIMP GetParameters(DWORD *pdwFlags, DWORD *pdwQueue)
+ {
+ Q_UNUSED(pdwFlags);
+ Q_UNUSED(pdwQueue);
+ return E_NOTIMPL;
+ }
+
+ void load(const QUrl &media, QIODevice *stream);
+ void stop(bool immediate = false);
+ void start();
+ void pause();
+
+ QMediaPlayer::MediaStatus status() const;
+ qint64 position();
+ void setPosition(qint64 position);
+ qreal playbackRate() const;
+ void setPlaybackRate(qreal rate);
+ int volume() const;
+ void setVolume(int volume);
+ bool isMuted() const;
+ void setMuted(bool muted);
+ int bufferStatus();
+ QMediaTimeRange availablePlaybackRanges();
+
+ void changeStatus(QMediaPlayer::MediaStatus newStatus);
+
+ void close();
+
+ void addProbe(MFAudioProbeControl* probe);
+ void removeProbe(MFAudioProbeControl* probe);
+ void addProbe(MFVideoProbeControl* probe);
+ void removeProbe(MFVideoProbeControl* probe);
+
+Q_SIGNALS:
+ void error(QMediaPlayer::Error error, QString errorString, bool isFatal);
+ void sessionEvent(IMFMediaEvent *sessionEvent);
+ void statusChanged();
+ void audioAvailable();
+ void videoAvailable();
+ void durationUpdate(qint64 duration);
+ void seekableUpdate(bool seekable);
+ void positionChanged(qint64 position);
+ void playbackRateChanged(qreal rate);
+ void volumeChanged(int volume);
+ void mutedChanged(bool muted);
+ void bufferStatusChanged(int percentFilled);
+
+private Q_SLOTS:
+ void handleMediaSourceReady();
+ void handleSessionEvent(IMFMediaEvent *sessionEvent);
+ void handleSourceError(long hr);
+
+private:
+ long m_cRef;
+ MFPlayerService *m_playerService;
+ IMFMediaSession *m_session;
+ IMFPresentationClock *m_presentationClock;
+ IMFRateControl *m_rateControl;
+ IMFRateSupport *m_rateSupport;
+ IMFAudioStreamVolume *m_volumeControl;
+ IPropertyStore *m_netsourceStatistics;
+ PROPVARIANT m_varStart;
+ UINT64 m_duration;
+
+ enum Command
+ {
+ CmdNone = 0,
+ CmdStop,
+ CmdStart,
+ CmdPause,
+ CmdSeek,
+ CmdSeekResume,
+ CmdStartAndSeek
+ };
+
+ void clear();
+ void setPositionInternal(qint64 position, Command requestCmd);
+ void setPlaybackRateInternal(qreal rate);
+ void commitRateChange(qreal rate, BOOL isThin);
+ bool canScrub() const;
+ void scrub(bool enableScrub);
+ bool m_scrubbing;
+ float m_restoreRate;
+
+ SourceResolver *m_sourceResolver;
+ HANDLE m_hCloseEvent;
+ bool m_closing;
+
+ enum MediaType
+ {
+ Unknown = 0,
+ Audio = 1,
+ Video = 2,
+ };
+ DWORD m_mediaTypes;
+
+ enum PendingState
+ {
+ NoPending = 0,
+ CmdPending,
+ SeekPending,
+ };
+
+ struct SeekState
+ {
+ void setCommand(Command cmd) {
+ prevCmd = command;
+ command = cmd;
+ }
+ Command command;
+ Command prevCmd;
+ float rate; // Playback rate
+ BOOL isThin; // Thinned playback?
+ qint64 start; // Start position
+ };
+ SeekState m_state; // Current nominal state.
+ SeekState m_request; // Pending request.
+ PendingState m_pendingState;
+ float m_pendingRate;
+ void updatePendingCommands(Command command);
+
+ QMediaPlayer::MediaStatus m_status;
+ bool m_canScrub;
+ int m_volume;
+ bool m_muted;
+
+ void setVolumeInternal(int volume);
+
+ void createSession();
+ void setupPlaybackTopology(IMFMediaSource *source, IMFPresentationDescriptor *sourcePD);
+ MediaType getStreamType(IMFStreamDescriptor *stream) const;
+ IMFTopologyNode* addSourceNode(IMFTopology* topology, IMFMediaSource* source,
+ IMFPresentationDescriptor* presentationDesc, IMFStreamDescriptor *streamDesc);
+ IMFTopologyNode* addOutputNode(MediaType mediaType, IMFTopology* topology, DWORD sinkID);
+
+ bool addAudioSampleGrabberNode(IMFTopology* topology);
+ bool setupAudioSampleGrabber(IMFTopology *topology, IMFTopologyNode *sourceNode, IMFTopologyNode *outputNode);
+ QAudioFormat audioFormatForMFMediaType(IMFMediaType *mediaType) const;
+ AudioSampleGrabberCallback *m_audioSampleGrabber;
+ IMFTopologyNode *m_audioSampleGrabberNode;
+
+ IMFTopology *insertMFT(IMFTopology *topology, TOPOID outputNodeId);
+ bool insertResizer(IMFTopology *topology);
+ void insertColorConverter(IMFTopology *topology, TOPOID outputNodeId);
+ MFTransform *m_videoProbeMFT;
+ QList<MFVideoProbeControl*> m_videoProbes;
+};
+
+
+#endif
diff --git a/src/multimedia/platform/wmf/player/mftvideo.cpp b/src/multimedia/platform/wmf/player/mftvideo.cpp
new file mode 100644
index 000000000..b2ff27f80
--- /dev/null
+++ b/src/multimedia/platform/wmf/player/mftvideo.cpp
@@ -0,0 +1,753 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $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 "mftvideo_p.h"
+#include "mfvideoprobecontrol_p.h"
+#include <private/qmemoryvideobuffer_p.h>
+#include <mferror.h>
+#include <strmif.h>
+#include <uuids.h>
+#include <InitGuid.h>
+#include <d3d9.h>
+#include <qdebug.h>
+
+// This MFT sends all samples it processes to connected video probes.
+// Sample is sent to probes in ProcessInput.
+// In ProcessOutput this MFT simply returns the original sample.
+
+// The implementation is based on a boilerplate from the MF SDK example.
+
+MFTransform::MFTransform():
+ m_cRef(1),
+ m_inputType(0),
+ m_outputType(0),
+ m_sample(0),
+ m_videoSinkTypeHandler(0),
+ m_bytesPerLine(0)
+{
+}
+
+MFTransform::~MFTransform()
+{
+ if (m_inputType)
+ m_inputType->Release();
+
+ if (m_outputType)
+ m_outputType->Release();
+
+ if (m_videoSinkTypeHandler)
+ m_videoSinkTypeHandler->Release();
+}
+
+void MFTransform::addProbe(MFVideoProbeControl *probe)
+{
+ QMutexLocker locker(&m_videoProbeMutex);
+
+ if (m_videoProbes.contains(probe))
+ return;
+
+ m_videoProbes.append(probe);
+}
+
+void MFTransform::removeProbe(MFVideoProbeControl *probe)
+{
+ QMutexLocker locker(&m_videoProbeMutex);
+ m_videoProbes.removeOne(probe);
+}
+
+void MFTransform::setVideoSink(IUnknown *videoSink)
+{
+ // This transform supports the same input types as the video sink.
+ // Store its type handler interface in order to report the correct supported types.
+
+ if (m_videoSinkTypeHandler) {
+ m_videoSinkTypeHandler->Release();
+ m_videoSinkTypeHandler = NULL;
+ }
+
+ if (videoSink)
+ videoSink->QueryInterface(IID_PPV_ARGS(&m_videoSinkTypeHandler));
+}
+
+STDMETHODIMP MFTransform::QueryInterface(REFIID riid, void** ppv)
+{
+ if (!ppv)
+ return E_POINTER;
+ if (riid == IID_IMFTransform) {
+ *ppv = static_cast<IMFTransform*>(this);
+ } else if (riid == IID_IUnknown) {
+ *ppv = static_cast<IUnknown*>(this);
+ } else {
+ *ppv = NULL;
+ return E_NOINTERFACE;
+ }
+ AddRef();
+ return S_OK;
+}
+
+STDMETHODIMP_(ULONG) MFTransform::AddRef()
+{
+ return InterlockedIncrement(&m_cRef);
+}
+
+STDMETHODIMP_(ULONG) MFTransform::Release()
+{
+ ULONG cRef = InterlockedDecrement(&m_cRef);
+ if (cRef == 0) {
+ delete this;
+ }
+ return cRef;
+}
+
+STDMETHODIMP MFTransform::GetStreamLimits(DWORD *pdwInputMinimum, DWORD *pdwInputMaximum, DWORD *pdwOutputMinimum, DWORD *pdwOutputMaximum)
+{
+ if (!pdwInputMinimum || !pdwInputMaximum || !pdwOutputMinimum || !pdwOutputMaximum)
+ return E_POINTER;
+ *pdwInputMinimum = 1;
+ *pdwInputMaximum = 1;
+ *pdwOutputMinimum = 1;
+ *pdwOutputMaximum = 1;
+ return S_OK;
+}
+
+STDMETHODIMP MFTransform::GetStreamCount(DWORD *pcInputStreams, DWORD *pcOutputStreams)
+{
+ if (!pcInputStreams || !pcOutputStreams)
+ return E_POINTER;
+
+ *pcInputStreams = 1;
+ *pcOutputStreams = 1;
+ return S_OK;
+}
+
+STDMETHODIMP MFTransform::GetStreamIDs(DWORD dwInputIDArraySize, DWORD *pdwInputIDs, DWORD dwOutputIDArraySize, DWORD *pdwOutputIDs)
+{
+ // streams are numbered consecutively
+ Q_UNUSED(dwInputIDArraySize);
+ Q_UNUSED(pdwInputIDs);
+ Q_UNUSED(dwOutputIDArraySize);
+ Q_UNUSED(pdwOutputIDs);
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP MFTransform::GetInputStreamInfo(DWORD dwInputStreamID, MFT_INPUT_STREAM_INFO *pStreamInfo)
+{
+ QMutexLocker locker(&m_mutex);
+
+ if (dwInputStreamID > 0)
+ return MF_E_INVALIDSTREAMNUMBER;
+
+ if (!pStreamInfo)
+ return E_POINTER;
+
+ pStreamInfo->cbSize = 0;
+ pStreamInfo->hnsMaxLatency = 0;
+ pStreamInfo->cbMaxLookahead = 0;
+ pStreamInfo->cbAlignment = 0;
+ pStreamInfo->dwFlags = MFT_INPUT_STREAM_WHOLE_SAMPLES
+ | MFT_INPUT_STREAM_SINGLE_SAMPLE_PER_BUFFER
+ | MFT_INPUT_STREAM_PROCESSES_IN_PLACE;
+
+ return S_OK;
+}
+
+STDMETHODIMP MFTransform::GetOutputStreamInfo(DWORD dwOutputStreamID, MFT_OUTPUT_STREAM_INFO *pStreamInfo)
+{
+ QMutexLocker locker(&m_mutex);
+
+ if (dwOutputStreamID > 0)
+ return MF_E_INVALIDSTREAMNUMBER;
+
+ if (!pStreamInfo)
+ return E_POINTER;
+
+ pStreamInfo->cbSize = 0;
+ pStreamInfo->cbAlignment = 0;
+ pStreamInfo->dwFlags = MFT_OUTPUT_STREAM_WHOLE_SAMPLES
+ | MFT_OUTPUT_STREAM_SINGLE_SAMPLE_PER_BUFFER
+ | MFT_OUTPUT_STREAM_PROVIDES_SAMPLES
+ | MFT_OUTPUT_STREAM_DISCARDABLE;
+
+ return S_OK;
+}
+
+STDMETHODIMP MFTransform::GetAttributes(IMFAttributes **pAttributes)
+{
+ // This MFT does not support attributes.
+ Q_UNUSED(pAttributes);
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP MFTransform::GetInputStreamAttributes(DWORD dwInputStreamID, IMFAttributes **pAttributes)
+{
+ // This MFT does not support input stream attributes.
+ Q_UNUSED(dwInputStreamID);
+ Q_UNUSED(pAttributes);
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP MFTransform::GetOutputStreamAttributes(DWORD dwOutputStreamID, IMFAttributes **pAttributes)
+{
+ // This MFT does not support output stream attributes.
+ Q_UNUSED(dwOutputStreamID);
+ Q_UNUSED(pAttributes);
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP MFTransform::DeleteInputStream(DWORD dwStreamID)
+{
+ // This MFT has a fixed number of input streams.
+ Q_UNUSED(dwStreamID);
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP MFTransform::AddInputStreams(DWORD cStreams, DWORD *adwStreamIDs)
+{
+ // This MFT has a fixed number of input streams.
+ Q_UNUSED(cStreams);
+ Q_UNUSED(adwStreamIDs);
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP MFTransform::GetInputAvailableType(DWORD dwInputStreamID, DWORD dwTypeIndex, IMFMediaType **ppType)
+{
+ // We support the same input types as the video sink
+ if (!m_videoSinkTypeHandler)
+ return E_NOTIMPL;
+
+ if (dwInputStreamID > 0)
+ return MF_E_INVALIDSTREAMNUMBER;
+
+ if (!ppType)
+ return E_POINTER;
+
+ return m_videoSinkTypeHandler->GetMediaTypeByIndex(dwTypeIndex, ppType);
+}
+
+STDMETHODIMP MFTransform::GetOutputAvailableType(DWORD dwOutputStreamID, DWORD dwTypeIndex, IMFMediaType **ppType)
+{
+ // Since we don't modify the samples, the output type must be the same as the input type.
+ // Report our input type as the only available output type.
+
+ if (dwOutputStreamID > 0)
+ return MF_E_INVALIDSTREAMNUMBER;
+
+ if (!ppType)
+ return E_POINTER;
+
+ // Input type must be set first
+ if (!m_inputType)
+ return MF_E_TRANSFORM_TYPE_NOT_SET;
+
+ if (dwTypeIndex > 0)
+ return MF_E_NO_MORE_TYPES;
+
+ // Return a copy to make sure our type is not modified
+ if (FAILED(MFCreateMediaType(ppType)))
+ return E_OUTOFMEMORY;
+
+ return m_inputType->CopyAllItems(*ppType);
+}
+
+STDMETHODIMP MFTransform::SetInputType(DWORD dwInputStreamID, IMFMediaType *pType, DWORD dwFlags)
+{
+ if (dwInputStreamID > 0)
+ return MF_E_INVALIDSTREAMNUMBER;
+
+ QMutexLocker locker(&m_mutex);
+
+ if (m_sample)
+ return MF_E_TRANSFORM_CANNOT_CHANGE_MEDIATYPE_WHILE_PROCESSING;
+
+ if (!isMediaTypeSupported(pType))
+ return MF_E_INVALIDMEDIATYPE;
+
+ if (dwFlags == MFT_SET_TYPE_TEST_ONLY)
+ return pType ? S_OK : E_POINTER;
+
+ if (m_inputType) {
+ m_inputType->Release();
+ // Input type has changed, discard output type (if it's set) so it's reset later on
+ DWORD flags = 0;
+ if (m_outputType && m_outputType->IsEqual(pType, &flags) != S_OK) {
+ m_outputType->Release();
+ m_outputType = 0;
+ }
+ }
+
+ m_inputType = pType;
+
+ if (m_inputType)
+ m_inputType->AddRef();
+
+ return S_OK;
+}
+
+STDMETHODIMP MFTransform::SetOutputType(DWORD dwOutputStreamID, IMFMediaType *pType, DWORD dwFlags)
+{
+ if (dwOutputStreamID > 0)
+ return MF_E_INVALIDSTREAMNUMBER;
+
+ if (dwFlags == MFT_SET_TYPE_TEST_ONLY && !pType)
+ return E_POINTER;
+
+ QMutexLocker locker(&m_mutex);
+
+ // Input type must be set first
+ if (!m_inputType)
+ return MF_E_TRANSFORM_TYPE_NOT_SET;
+
+ if (m_sample)
+ return MF_E_TRANSFORM_CANNOT_CHANGE_MEDIATYPE_WHILE_PROCESSING;
+
+ DWORD flags = 0;
+ if (pType && m_inputType->IsEqual(pType, &flags) != S_OK)
+ return MF_E_INVALIDMEDIATYPE;
+
+ if (dwFlags == MFT_SET_TYPE_TEST_ONLY)
+ return pType ? S_OK : E_POINTER;
+
+ if (m_outputType)
+ m_outputType->Release();
+
+ m_outputType = pType;
+
+ if (m_outputType) {
+ m_outputType->AddRef();
+ m_format = videoFormatForMFMediaType(m_outputType, &m_bytesPerLine);
+ }
+
+ return S_OK;
+}
+
+STDMETHODIMP MFTransform::GetInputCurrentType(DWORD dwInputStreamID, IMFMediaType **ppType)
+{
+ if (dwInputStreamID > 0)
+ return MF_E_INVALIDSTREAMNUMBER;
+
+ if (ppType == NULL)
+ return E_POINTER;
+
+ QMutexLocker locker(&m_mutex);
+
+ if (!m_inputType)
+ return MF_E_TRANSFORM_TYPE_NOT_SET;
+
+ // Return a copy to make sure our type is not modified
+ if (FAILED(MFCreateMediaType(ppType)))
+ return E_OUTOFMEMORY;
+
+ return m_inputType->CopyAllItems(*ppType);
+}
+
+STDMETHODIMP MFTransform::GetOutputCurrentType(DWORD dwOutputStreamID, IMFMediaType **ppType)
+{
+ if (dwOutputStreamID > 0)
+ return MF_E_INVALIDSTREAMNUMBER;
+
+ if (ppType == NULL)
+ return E_POINTER;
+
+ QMutexLocker locker(&m_mutex);
+
+ if (!m_outputType)
+ return MF_E_TRANSFORM_TYPE_NOT_SET;
+
+ // Return a copy to make sure our type is not modified
+ if (FAILED(MFCreateMediaType(ppType)))
+ return E_OUTOFMEMORY;
+
+ return m_outputType->CopyAllItems(*ppType);
+}
+
+STDMETHODIMP MFTransform::GetInputStatus(DWORD dwInputStreamID, DWORD *pdwFlags)
+{
+ if (dwInputStreamID > 0)
+ return MF_E_INVALIDSTREAMNUMBER;
+
+ if (!pdwFlags)
+ return E_POINTER;
+
+ QMutexLocker locker(&m_mutex);
+
+ if (!m_inputType || !m_outputType)
+ return MF_E_TRANSFORM_TYPE_NOT_SET;
+
+ if (m_sample)
+ *pdwFlags = 0;
+ else
+ *pdwFlags = MFT_INPUT_STATUS_ACCEPT_DATA;
+
+ return S_OK;
+}
+
+STDMETHODIMP MFTransform::GetOutputStatus(DWORD *pdwFlags)
+{
+ if (!pdwFlags)
+ return E_POINTER;
+
+ QMutexLocker locker(&m_mutex);
+
+ if (!m_inputType || !m_outputType)
+ return MF_E_TRANSFORM_TYPE_NOT_SET;
+
+ if (m_sample)
+ *pdwFlags = MFT_OUTPUT_STATUS_SAMPLE_READY;
+ else
+ *pdwFlags = 0;
+
+ return S_OK;
+}
+
+STDMETHODIMP MFTransform::SetOutputBounds(LONGLONG hnsLowerBound, LONGLONG hnsUpperBound)
+{
+ Q_UNUSED(hnsLowerBound);
+ Q_UNUSED(hnsUpperBound);
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP MFTransform::ProcessEvent(DWORD dwInputStreamID, IMFMediaEvent *pEvent)
+{
+ // This MFT ignores all events, and the pipeline should send all events downstream.
+ Q_UNUSED(dwInputStreamID);
+ Q_UNUSED(pEvent);
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP MFTransform::ProcessMessage(MFT_MESSAGE_TYPE eMessage, ULONG_PTR ulParam)
+{
+ Q_UNUSED(ulParam);
+
+ HRESULT hr = S_OK;
+
+ switch (eMessage)
+ {
+ case MFT_MESSAGE_COMMAND_FLUSH:
+ hr = OnFlush();
+ break;
+
+ case MFT_MESSAGE_COMMAND_DRAIN:
+ // Drain: Tells the MFT not to accept any more input until
+ // all of the pending output has been processed. That is our
+ // default behevior already, so there is nothing to do.
+ break;
+
+ case MFT_MESSAGE_SET_D3D_MANAGER:
+ // The pipeline should never send this message unless the MFT
+ // has the MF_SA_D3D_AWARE attribute set to TRUE. However, if we
+ // do get this message, it's invalid and we don't implement it.
+ hr = E_NOTIMPL;
+ break;
+
+ // The remaining messages do not require any action from this MFT.
+ case MFT_MESSAGE_NOTIFY_BEGIN_STREAMING:
+ case MFT_MESSAGE_NOTIFY_END_STREAMING:
+ case MFT_MESSAGE_NOTIFY_END_OF_STREAM:
+ case MFT_MESSAGE_NOTIFY_START_OF_STREAM:
+ break;
+ }
+
+ return hr;
+}
+
+STDMETHODIMP MFTransform::ProcessInput(DWORD dwInputStreamID, IMFSample *pSample, DWORD dwFlags)
+{
+ if (dwInputStreamID > 0)
+ return MF_E_INVALIDSTREAMNUMBER;
+
+ if (dwFlags != 0)
+ return E_INVALIDARG; // dwFlags is reserved and must be zero.
+
+ QMutexLocker locker(&m_mutex);
+
+ if (!m_inputType)
+ return MF_E_TRANSFORM_TYPE_NOT_SET;
+
+ if (m_sample)
+ return MF_E_NOTACCEPTING;
+
+ // Validate the number of buffers. There should only be a single buffer to hold the video frame.
+ DWORD dwBufferCount = 0;
+ HRESULT hr = pSample->GetBufferCount(&dwBufferCount);
+ if (FAILED(hr))
+ return hr;
+
+ if (dwBufferCount == 0)
+ return E_FAIL;
+
+ if (dwBufferCount > 1)
+ return MF_E_SAMPLE_HAS_TOO_MANY_BUFFERS;
+
+ m_sample = pSample;
+ m_sample->AddRef();
+
+ QMutexLocker lockerProbe(&m_videoProbeMutex);
+
+ if (!m_videoProbes.isEmpty()) {
+ QVideoFrame frame = makeVideoFrame();
+
+ for (MFVideoProbeControl* probe : qAsConst(m_videoProbes))
+ probe->bufferProbed(frame);
+ }
+
+ return S_OK;
+}
+
+STDMETHODIMP MFTransform::ProcessOutput(DWORD dwFlags, DWORD cOutputBufferCount, MFT_OUTPUT_DATA_BUFFER *pOutputSamples, DWORD *pdwStatus)
+{
+ if (pOutputSamples == NULL || pdwStatus == NULL)
+ return E_POINTER;
+
+ if (cOutputBufferCount != 1)
+ return E_INVALIDARG;
+
+ QMutexLocker locker(&m_mutex);
+
+ if (!m_inputType)
+ return MF_E_TRANSFORM_TYPE_NOT_SET;
+
+ if (!m_outputType) {
+ pOutputSamples[0].dwStatus = MFT_OUTPUT_DATA_BUFFER_FORMAT_CHANGE;
+ return MF_E_TRANSFORM_STREAM_CHANGE;
+ }
+
+ IMFMediaBuffer *input = NULL;
+ IMFMediaBuffer *output = NULL;
+
+ if (dwFlags == MFT_PROCESS_OUTPUT_DISCARD_WHEN_NO_BUFFER)
+ goto done;
+ else if (dwFlags != 0)
+ return E_INVALIDARG;
+
+ if (!m_sample)
+ return MF_E_TRANSFORM_NEED_MORE_INPUT;
+
+ // Since the MFT_OUTPUT_STREAM_PROVIDES_SAMPLES flag is set, the client
+ // should not be providing samples here
+ if (pOutputSamples[0].pSample != NULL)
+ return E_INVALIDARG;
+
+ pOutputSamples[0].pSample = m_sample;
+ pOutputSamples[0].pSample->AddRef();
+
+ // Send video frame to probes
+ // We do it here (instead of inside ProcessInput) to make sure samples discarded by the renderer
+ // are not sent.
+ m_videoProbeMutex.lock();
+ if (!m_videoProbes.isEmpty()) {
+ QVideoFrame frame = makeVideoFrame();
+
+ for (MFVideoProbeControl* probe : qAsConst(m_videoProbes))
+ probe->bufferProbed(frame);
+ }
+ m_videoProbeMutex.unlock();
+
+done:
+ pOutputSamples[0].dwStatus = 0;
+ *pdwStatus = 0;
+
+ m_sample->Release();
+ m_sample = 0;
+
+ if (input)
+ input->Release();
+ if (output)
+ output->Release();
+
+ return S_OK;
+}
+
+HRESULT MFTransform::OnFlush()
+{
+ QMutexLocker locker(&m_mutex);
+
+ if (m_sample) {
+ m_sample->Release();
+ m_sample = 0;
+ }
+ return S_OK;
+}
+
+QVideoFrame::PixelFormat MFTransform::formatFromSubtype(const GUID& subtype)
+{
+ if (subtype == MFVideoFormat_ARGB32)
+ return QVideoFrame::Format_ARGB32;
+ else if (subtype == MFVideoFormat_RGB32)
+ return QVideoFrame::Format_RGB32;
+ else if (subtype == MFVideoFormat_RGB24)
+ return QVideoFrame::Format_RGB24;
+ else if (subtype == MFVideoFormat_RGB565)
+ return QVideoFrame::Format_RGB565;
+ else if (subtype == MFVideoFormat_RGB555)
+ return QVideoFrame::Format_RGB555;
+ else if (subtype == MFVideoFormat_AYUV)
+ return QVideoFrame::Format_AYUV444;
+ else if (subtype == MFVideoFormat_I420)
+ return QVideoFrame::Format_YUV420P;
+ else if (subtype == MFVideoFormat_UYVY)
+ return QVideoFrame::Format_UYVY;
+ else if (subtype == MFVideoFormat_YV12)
+ return QVideoFrame::Format_YV12;
+ else if (subtype == MFVideoFormat_NV12)
+ return QVideoFrame::Format_NV12;
+
+ return QVideoFrame::Format_Invalid;
+}
+
+QVideoSurfaceFormat MFTransform::videoFormatForMFMediaType(IMFMediaType *mediaType, int *bytesPerLine)
+{
+ UINT32 stride;
+ if (FAILED(mediaType->GetUINT32(MF_MT_DEFAULT_STRIDE, &stride))) {
+ *bytesPerLine = 0;
+ return QVideoSurfaceFormat();
+ }
+
+ *bytesPerLine = (int)stride;
+
+ QSize size;
+ UINT32 width, height;
+ if (FAILED(MFGetAttributeSize(mediaType, MF_MT_FRAME_SIZE, &width, &height)))
+ return QVideoSurfaceFormat();
+
+ size.setWidth(width);
+ size.setHeight(height);
+
+ GUID subtype = GUID_NULL;
+ if (FAILED(mediaType->GetGUID(MF_MT_SUBTYPE, &subtype)))
+ return QVideoSurfaceFormat();
+
+ QVideoFrame::PixelFormat pixelFormat = formatFromSubtype(subtype);
+ QVideoSurfaceFormat format(size, pixelFormat);
+
+ UINT32 num, den;
+ if (SUCCEEDED(MFGetAttributeRatio(mediaType, MF_MT_PIXEL_ASPECT_RATIO, &num, &den))) {
+ format.setPixelAspectRatio(num, den);
+ }
+ if (SUCCEEDED(MFGetAttributeRatio(mediaType, MF_MT_FRAME_RATE, &num, &den))) {
+ format.setFrameRate(qreal(num)/den);
+ }
+
+ return format;
+}
+
+QVideoFrame MFTransform::makeVideoFrame()
+{
+ QVideoFrame frame;
+
+ if (!m_format.isValid())
+ return frame;
+
+ IMFMediaBuffer *buffer = 0;
+
+ do {
+ if (FAILED(m_sample->ConvertToContiguousBuffer(&buffer)))
+ break;
+
+ QByteArray array = dataFromBuffer(buffer, m_format.frameHeight(), &m_bytesPerLine);
+ if (array.isEmpty())
+ break;
+
+ // Wrapping IMFSample or IMFMediaBuffer in a QVideoFrame is not possible because we cannot hold
+ // IMFSample for a "long" time without affecting the rest of the topology.
+ // If IMFSample is held for more than 5 frames decoder starts to reuse it even though it hasn't been released it yet.
+ // That is why we copy data from IMFMediaBuffer here.
+ frame = QVideoFrame(new QMemoryVideoBuffer(array, m_bytesPerLine), m_format.frameSize(), m_format.pixelFormat());
+
+ // WMF uses 100-nanosecond units, Qt uses microseconds
+ LONGLONG startTime = -1;
+ if (SUCCEEDED(m_sample->GetSampleTime(&startTime))) {
+ frame.setStartTime(startTime * 0.1);
+
+ LONGLONG duration = -1;
+ if (SUCCEEDED(m_sample->GetSampleDuration(&duration)))
+ frame.setEndTime((startTime + duration) * 0.1);
+ }
+ } while (false);
+
+ if (buffer)
+ buffer->Release();
+
+ return frame;
+}
+
+QByteArray MFTransform::dataFromBuffer(IMFMediaBuffer *buffer, int height, int *bytesPerLine)
+{
+ QByteArray array;
+ BYTE *bytes;
+ DWORD length;
+ HRESULT hr = buffer->Lock(&bytes, NULL, &length);
+ if (SUCCEEDED(hr)) {
+ array = QByteArray((const char *)bytes, (int)length);
+ buffer->Unlock();
+ } else {
+ // try to lock as Direct3DSurface
+ IDirect3DSurface9 *surface = 0;
+ do {
+ if (FAILED(MFGetService(buffer, MR_BUFFER_SERVICE, IID_IDirect3DSurface9, (void**)&surface)))
+ break;
+
+ D3DLOCKED_RECT rect;
+ if (FAILED(surface->LockRect(&rect, NULL, D3DLOCK_READONLY)))
+ break;
+
+ if (bytesPerLine)
+ *bytesPerLine = (int)rect.Pitch;
+
+ array = QByteArray((const char *)rect.pBits, rect.Pitch * height);
+ surface->UnlockRect();
+ } while (false);
+
+ if (surface) {
+ surface->Release();
+ surface = 0;
+ }
+ }
+
+ return array;
+}
+
+bool MFTransform::isMediaTypeSupported(IMFMediaType *type)
+{
+ // If we don't have the video sink's type handler,
+ // assume it supports anything...
+ if (!m_videoSinkTypeHandler || !type)
+ return true;
+
+ return m_videoSinkTypeHandler->IsMediaTypeSupported(type, NULL) == S_OK;
+}
diff --git a/src/multimedia/platform/wmf/player/mftvideo_p.h b/src/multimedia/platform/wmf/player/mftvideo_p.h
new file mode 100644
index 000000000..e08f0977f
--- /dev/null
+++ b/src/multimedia/platform/wmf/player/mftvideo_p.h
@@ -0,0 +1,128 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $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$
+**
+****************************************************************************/
+
+#ifndef MFTRANSFORM_H
+#define MFTRANSFORM_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <mfapi.h>
+#include <mfidl.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qmutex.h>
+#include <QtMultimedia/qvideosurfaceformat.h>
+
+QT_USE_NAMESPACE
+
+class MFVideoProbeControl;
+
+class MFTransform: public IMFTransform
+{
+public:
+ MFTransform();
+ ~MFTransform();
+
+ void addProbe(MFVideoProbeControl* probe);
+ void removeProbe(MFVideoProbeControl* probe);
+
+ void setVideoSink(IUnknown *videoSink);
+
+ // IUnknown methods
+ STDMETHODIMP QueryInterface(REFIID iid, void** ppv);
+ STDMETHODIMP_(ULONG) AddRef();
+ STDMETHODIMP_(ULONG) Release();
+
+ // IMFTransform methods
+ STDMETHODIMP GetStreamLimits(DWORD *pdwInputMinimum, DWORD *pdwInputMaximum, DWORD *pdwOutputMinimum, DWORD *pdwOutputMaximum);
+ STDMETHODIMP GetStreamCount(DWORD *pcInputStreams, DWORD *pcOutputStreams);
+ STDMETHODIMP GetStreamIDs(DWORD dwInputIDArraySize, DWORD *pdwInputIDs, DWORD dwOutputIDArraySize, DWORD *pdwOutputIDs);
+ STDMETHODIMP GetInputStreamInfo(DWORD dwInputStreamID, MFT_INPUT_STREAM_INFO *pStreamInfo);
+ STDMETHODIMP GetOutputStreamInfo(DWORD dwOutputStreamID, MFT_OUTPUT_STREAM_INFO *pStreamInfo);
+ STDMETHODIMP GetAttributes(IMFAttributes **pAttributes);
+ STDMETHODIMP GetInputStreamAttributes(DWORD dwInputStreamID, IMFAttributes **pAttributes);
+ STDMETHODIMP GetOutputStreamAttributes(DWORD dwOutputStreamID, IMFAttributes **pAttributes);
+ STDMETHODIMP DeleteInputStream(DWORD dwStreamID);
+ STDMETHODIMP AddInputStreams(DWORD cStreams, DWORD *adwStreamIDs);
+ STDMETHODIMP GetInputAvailableType(DWORD dwInputStreamID, DWORD dwTypeIndex, IMFMediaType **ppType);
+ STDMETHODIMP GetOutputAvailableType(DWORD dwOutputStreamID,DWORD dwTypeIndex, IMFMediaType **ppType);
+ STDMETHODIMP SetInputType(DWORD dwInputStreamID, IMFMediaType *pType, DWORD dwFlags);
+ STDMETHODIMP SetOutputType(DWORD dwOutputStreamID, IMFMediaType *pType, DWORD dwFlags);
+ STDMETHODIMP GetInputCurrentType(DWORD dwInputStreamID, IMFMediaType **ppType);
+ STDMETHODIMP GetOutputCurrentType(DWORD dwOutputStreamID, IMFMediaType **ppType);
+ STDMETHODIMP GetInputStatus(DWORD dwInputStreamID, DWORD *pdwFlags);
+ STDMETHODIMP GetOutputStatus(DWORD *pdwFlags);
+ STDMETHODIMP SetOutputBounds(LONGLONG hnsLowerBound, LONGLONG hnsUpperBound);
+ STDMETHODIMP ProcessEvent(DWORD dwInputStreamID, IMFMediaEvent *pEvent);
+ STDMETHODIMP ProcessMessage(MFT_MESSAGE_TYPE eMessage, ULONG_PTR ulParam);
+ STDMETHODIMP ProcessInput(DWORD dwInputStreamID, IMFSample *pSample, DWORD dwFlags);
+ STDMETHODIMP ProcessOutput(DWORD dwFlags, DWORD cOutputBufferCount, MFT_OUTPUT_DATA_BUFFER *pOutputSamples, DWORD *pdwStatus);
+
+private:
+ HRESULT OnFlush();
+ static QVideoFrame::PixelFormat formatFromSubtype(const GUID& subtype);
+ static QVideoSurfaceFormat videoFormatForMFMediaType(IMFMediaType *mediaType, int *bytesPerLine);
+ QVideoFrame makeVideoFrame();
+ QByteArray dataFromBuffer(IMFMediaBuffer *buffer, int height, int *bytesPerLine);
+ bool isMediaTypeSupported(IMFMediaType *type);
+
+ long m_cRef;
+ IMFMediaType *m_inputType;
+ IMFMediaType *m_outputType;
+ IMFSample *m_sample;
+ QMutex m_mutex;
+
+ IMFMediaTypeHandler *m_videoSinkTypeHandler;
+
+ QList<MFVideoProbeControl*> m_videoProbes;
+ QMutex m_videoProbeMutex;
+
+ QVideoSurfaceFormat m_format;
+ int m_bytesPerLine;
+};
+
+#endif
diff --git a/src/multimedia/platform/wmf/player/mfvideoprobecontrol.cpp b/src/multimedia/platform/wmf/player/mfvideoprobecontrol.cpp
new file mode 100644
index 000000000..e4d1a8b23
--- /dev/null
+++ b/src/multimedia/platform/wmf/player/mfvideoprobecontrol.cpp
@@ -0,0 +1,54 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $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 "mfvideoprobecontrol_p.h"
+
+MFVideoProbeControl::MFVideoProbeControl(QObject *parent)
+ : QMediaVideoProbeControl(parent)
+{
+}
+
+MFVideoProbeControl::~MFVideoProbeControl()
+{
+}
+
+void MFVideoProbeControl::bufferProbed(const QVideoFrame& frame)
+{
+ QMetaObject::invokeMethod(this, "videoFrameProbed", Qt::QueuedConnection, Q_ARG(QVideoFrame, frame));
+}
diff --git a/src/multimedia/platform/wmf/player/mfvideoprobecontrol_p.h b/src/multimedia/platform/wmf/player/mfvideoprobecontrol_p.h
new file mode 100644
index 000000000..a7f79ef92
--- /dev/null
+++ b/src/multimedia/platform/wmf/player/mfvideoprobecontrol_p.h
@@ -0,0 +1,70 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $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$
+**
+****************************************************************************/
+
+#ifndef MFVIDEOPROBECONTROL_H
+#define MFVIDEOPROBECONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <qmediavideoprobecontrol.h>
+#include <QtCore/qmutex.h>
+#include <qvideoframe.h>
+
+QT_USE_NAMESPACE
+
+class MFVideoProbeControl : public QMediaVideoProbeControl
+{
+ Q_OBJECT
+public:
+ explicit MFVideoProbeControl(QObject *parent);
+ virtual ~MFVideoProbeControl();
+
+ void bufferProbed(const QVideoFrame& frame);
+};
+
+#endif
diff --git a/src/multimedia/platform/wmf/player/mfvideorenderercontrol.cpp b/src/multimedia/platform/wmf/player/mfvideorenderercontrol.cpp
new file mode 100644
index 000000000..2acde7e4c
--- /dev/null
+++ b/src/multimedia/platform/wmf/player/mfvideorenderercontrol.cpp
@@ -0,0 +1,2424 @@
+/****************************************************************************
+**
+** 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_p.h"
+#include "mfactivate_p.h"
+
+#include "evrcustompresenter_p.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();
+ }
+
+ MapData map(MapMode mode) override
+ {
+ MapData mapData;
+ if (m_mapMode == NotMapped && mode != NotMapped) {
+ BYTE *bytes;
+ DWORD length;
+ HRESULT hr = m_buffer->Lock(&bytes, NULL, &length);
+ if (SUCCEEDED(hr)) {
+ mapData.nBytes = qsizetype(length);
+ mapData.nPlanes = 1;
+ mapData.bytesPerLine[0] = m_bytesPerLine;
+ mapData.data[0] = reinterpret_cast<uchar *>(bytes);
+ m_mapMode = mode;
+ } else {
+ qWarning("Faild to lock mf buffer!");
+ }
+ }
+ return mapData;
+ }
+
+ void unmap() override
+ {
+ if (m_mapMode == NotMapped)
+ return;
+ m_mapMode = NotMapped;
+ m_buffer->Unlock();
+ }
+
+ MapMode mapMode() const override
+ {
+ 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_p.cpp"
+#include "mfvideorenderercontrol.moc"
diff --git a/src/multimedia/platform/wmf/player/mfvideorenderercontrol_p.h b/src/multimedia/platform/wmf/player/mfvideorenderercontrol_p.h
new file mode 100644
index 000000000..dd8a6a278
--- /dev/null
+++ b/src/multimedia/platform/wmf/player/mfvideorenderercontrol_p.h
@@ -0,0 +1,92 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef MFVIDEORENDERERCONTROL_H
+#define MFVIDEORENDERERCONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qvideorenderercontrol.h"
+#include <mfapi.h>
+#include <mfidl.h>
+
+QT_USE_NAMESPACE
+
+class EVRCustomPresenterActivate;
+
+class MFVideoRendererControl : public QVideoRendererControl
+{
+ Q_OBJECT
+public:
+ MFVideoRendererControl(QObject *parent = 0);
+ ~MFVideoRendererControl();
+
+ QAbstractVideoSurface *surface() const;
+ void setSurface(QAbstractVideoSurface *surface);
+
+ IMFActivate* createActivate();
+ void releaseActivate();
+
+protected:
+ void customEvent(QEvent *event);
+
+private Q_SLOTS:
+ void supportedFormatsChanged();
+ void present();
+
+private:
+ void clear();
+
+ QAbstractVideoSurface *m_surface;
+ IMFActivate *m_currentActivate;
+ IMFSampleGrabberSinkCallback *m_callback;
+
+ EVRCustomPresenterActivate *m_presenterActivate;
+};
+
+#endif
diff --git a/src/multimedia/platform/wmf/player/player.pri b/src/multimedia/platform/wmf/player/player.pri
new file mode 100644
index 000000000..3263b84ba
--- /dev/null
+++ b/src/multimedia/platform/wmf/player/player.pri
@@ -0,0 +1,32 @@
+INCLUDEPATH += $$PWD
+
+LIBS += -lgdi32 -luser32
+QMAKE_USE += wmf
+
+HEADERS += \
+ $$PWD/mfplayerservice_p.h \
+ $$PWD/mfplayersession_p.h \
+ $$PWD/mfplayercontrol_p.h \
+ $$PWD/mfvideorenderercontrol_p.h \
+ $$PWD/mfaudioendpointcontrol_p.h \
+ $$PWD/mfmetadatacontrol_p.h \
+ $$PWD/mfaudioprobecontrol_p.h \
+ $$PWD/mfvideoprobecontrol_p.h \
+ $$PWD/mfevrvideowindowcontrol_p.h \
+ $$PWD/samplegrabber_p.h \
+ $$PWD/mftvideo_p.h \
+ $$PWD/mfactivate_p.h
+
+SOURCES += \
+ $$PWD/mfplayerservice.cpp \
+ $$PWD/mfplayersession.cpp \
+ $$PWD/mfplayercontrol.cpp \
+ $$PWD/mfvideorenderercontrol.cpp \
+ $$PWD/mfaudioendpointcontrol.cpp \
+ $$PWD/mfmetadatacontrol.cpp \
+ $$PWD/mfaudioprobecontrol.cpp \
+ $$PWD/mfvideoprobecontrol.cpp \
+ $$PWD/mfevrvideowindowcontrol.cpp \
+ $$PWD/samplegrabber.cpp \
+ $$PWD/mftvideo.cpp \
+ $$PWD/mfactivate.cpp
diff --git a/src/multimedia/platform/wmf/player/samplegrabber.cpp b/src/multimedia/platform/wmf/player/samplegrabber.cpp
new file mode 100644
index 000000000..6c14dc152
--- /dev/null
+++ b/src/multimedia/platform/wmf/player/samplegrabber.cpp
@@ -0,0 +1,173 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $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 "samplegrabber_p.h"
+#include "mfaudioprobecontrol_p.h"
+
+STDMETHODIMP SampleGrabberCallback::QueryInterface(REFIID riid, void** ppv)
+{
+ if (!ppv)
+ return E_POINTER;
+ if (riid == IID_IMFSampleGrabberSinkCallback) {
+ *ppv = static_cast<IMFSampleGrabberSinkCallback*>(this);
+ } else if (riid == IID_IMFClockStateSink) {
+ *ppv = static_cast<IMFClockStateSink*>(this);
+ } else if (riid == IID_IUnknown) {
+ *ppv = static_cast<IUnknown*>(this);
+ } else {
+ *ppv = NULL;
+ return E_NOINTERFACE;
+ }
+ AddRef();
+ return S_OK;
+}
+
+STDMETHODIMP_(ULONG) SampleGrabberCallback::AddRef()
+{
+ return InterlockedIncrement(&m_cRef);
+}
+
+STDMETHODIMP_(ULONG) SampleGrabberCallback::Release()
+{
+ ULONG cRef = InterlockedDecrement(&m_cRef);
+ if (cRef == 0) {
+ delete this;
+ }
+ return cRef;
+
+}
+
+// IMFClockStateSink methods.
+
+STDMETHODIMP SampleGrabberCallback::OnClockStart(MFTIME hnsSystemTime, LONGLONG llClockStartOffset)
+{
+ Q_UNUSED(hnsSystemTime);
+ Q_UNUSED(llClockStartOffset);
+ return S_OK;
+}
+
+STDMETHODIMP SampleGrabberCallback::OnClockStop(MFTIME hnsSystemTime)
+{
+ Q_UNUSED(hnsSystemTime);
+ return S_OK;
+}
+
+STDMETHODIMP SampleGrabberCallback::OnClockPause(MFTIME hnsSystemTime)
+{
+ Q_UNUSED(hnsSystemTime);
+ return S_OK;
+}
+
+STDMETHODIMP SampleGrabberCallback::OnClockRestart(MFTIME hnsSystemTime)
+{
+ Q_UNUSED(hnsSystemTime);
+ return S_OK;
+}
+
+STDMETHODIMP SampleGrabberCallback::OnClockSetRate(MFTIME hnsSystemTime, float flRate)
+{
+ Q_UNUSED(hnsSystemTime);
+ Q_UNUSED(flRate);
+ return S_OK;
+}
+
+// IMFSampleGrabberSink methods.
+
+STDMETHODIMP SampleGrabberCallback::OnSetPresentationClock(IMFPresentationClock* pClock)
+{
+ Q_UNUSED(pClock);
+ return S_OK;
+}
+
+STDMETHODIMP SampleGrabberCallback::OnShutdown()
+{
+ return S_OK;
+}
+
+void AudioSampleGrabberCallback::addProbe(MFAudioProbeControl* probe)
+{
+ QMutexLocker locker(&m_audioProbeMutex);
+
+ if (m_audioProbes.contains(probe))
+ return;
+
+ m_audioProbes.append(probe);
+}
+
+void AudioSampleGrabberCallback::removeProbe(MFAudioProbeControl* probe)
+{
+ QMutexLocker locker(&m_audioProbeMutex);
+ m_audioProbes.removeOne(probe);
+}
+
+void AudioSampleGrabberCallback::setFormat(const QAudioFormat& format)
+{
+ m_format = format;
+}
+
+STDMETHODIMP AudioSampleGrabberCallback::OnProcessSample(REFGUID guidMajorMediaType, DWORD dwSampleFlags,
+ LONGLONG llSampleTime, LONGLONG llSampleDuration, const BYTE * pSampleBuffer,
+ DWORD dwSampleSize)
+{
+ Q_UNUSED(dwSampleFlags);
+ Q_UNUSED(llSampleTime);
+ Q_UNUSED(llSampleDuration);
+
+ if (guidMajorMediaType != GUID_NULL && guidMajorMediaType != MFMediaType_Audio)
+ return S_OK;
+
+ QMutexLocker locker(&m_audioProbeMutex);
+
+ if (m_audioProbes.isEmpty())
+ return S_OK;
+
+ // Check if sample has a presentation time
+ if (llSampleTime == _I64_MAX) {
+ // Set default QAudioBuffer start time
+ llSampleTime = -1;
+ } else {
+ // WMF uses 100-nanosecond units, Qt uses microseconds
+ llSampleTime /= 10;
+ }
+
+ for (MFAudioProbeControl* probe : qAsConst(m_audioProbes))
+ probe->bufferProbed((const char*)pSampleBuffer, dwSampleSize, m_format, llSampleTime);
+
+ return S_OK;
+}
diff --git a/src/multimedia/platform/wmf/player/samplegrabber_p.h b/src/multimedia/platform/wmf/player/samplegrabber_p.h
new file mode 100644
index 000000000..74eb62065
--- /dev/null
+++ b/src/multimedia/platform/wmf/player/samplegrabber_p.h
@@ -0,0 +1,107 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $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$
+**
+****************************************************************************/
+
+#ifndef SAMPLEGRABBER_H
+#define SAMPLEGRABBER_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qmutex.h>
+#include <QtCore/qlist.h>
+#include <QtMultimedia/qaudioformat.h>
+#include <mfapi.h>
+#include <mfidl.h>
+
+class MFAudioProbeControl;
+
+class SampleGrabberCallback : public IMFSampleGrabberSinkCallback
+{
+public:
+ // IUnknown methods
+ STDMETHODIMP QueryInterface(REFIID iid, void** ppv);
+ STDMETHODIMP_(ULONG) AddRef();
+ STDMETHODIMP_(ULONG) Release();
+
+ // IMFClockStateSink methods
+ STDMETHODIMP OnClockStart(MFTIME hnsSystemTime, LONGLONG llClockStartOffset);
+ STDMETHODIMP OnClockStop(MFTIME hnsSystemTime);
+ STDMETHODIMP OnClockPause(MFTIME hnsSystemTime);
+ STDMETHODIMP OnClockRestart(MFTIME hnsSystemTime);
+ STDMETHODIMP OnClockSetRate(MFTIME hnsSystemTime, float flRate);
+
+ // IMFSampleGrabberSinkCallback methods
+ STDMETHODIMP OnSetPresentationClock(IMFPresentationClock* pClock);
+ STDMETHODIMP OnShutdown();
+
+protected:
+ SampleGrabberCallback() : m_cRef(1) {}
+
+public:
+ virtual ~SampleGrabberCallback() {}
+
+private:
+ long m_cRef;
+};
+
+class AudioSampleGrabberCallback: public SampleGrabberCallback {
+public:
+ void addProbe(MFAudioProbeControl* probe);
+ void removeProbe(MFAudioProbeControl* probe);
+ void setFormat(const QAudioFormat& format);
+
+ STDMETHODIMP OnProcessSample(REFGUID guidMajorMediaType, DWORD dwSampleFlags,
+ LONGLONG llSampleTime, LONGLONG llSampleDuration, const BYTE * pSampleBuffer,
+ DWORD dwSampleSize);
+
+private:
+ QList<MFAudioProbeControl*> m_audioProbes;
+ QMutex m_audioProbeMutex;
+ QAudioFormat m_format;
+};
+
+#endif // SAMPLEGRABBER_H
diff --git a/src/multimedia/platform/wmf/sourceresolver.cpp b/src/multimedia/platform/wmf/sourceresolver.cpp
new file mode 100644
index 000000000..93af15a74
--- /dev/null
+++ b/src/multimedia/platform/wmf/sourceresolver.cpp
@@ -0,0 +1,325 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $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 "mfstream_p.h"
+#include "sourceresolver_p.h"
+#include <mferror.h>
+#include <nserror.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qdebug.h>
+#include <QtMultimedia/qmediaplayer.h>
+
+/*
+ SourceResolver is separated from MFPlayerSession to handle the work of resolving a media source
+ asynchronously. You call SourceResolver::load to request resolving a media source asynchronously,
+ and it will emit mediaSourceReady() when resolving is done. You can call SourceResolver::cancel to
+ stop the previous load operation if there is any.
+*/
+
+SourceResolver::SourceResolver()
+ : m_cRef(1)
+ , m_cancelCookie(0)
+ , m_sourceResolver(0)
+ , m_mediaSource(0)
+ , m_stream(0)
+{
+}
+
+SourceResolver::~SourceResolver()
+{
+ shutdown();
+ if (m_mediaSource) {
+ m_mediaSource->Release();
+ m_mediaSource = NULL;
+ }
+
+ if (m_cancelCookie)
+ m_cancelCookie->Release();
+ if (m_sourceResolver)
+ m_sourceResolver->Release();
+}
+
+STDMETHODIMP SourceResolver::QueryInterface(REFIID riid, LPVOID *ppvObject)
+{
+ if (!ppvObject)
+ return E_POINTER;
+ if (riid == IID_IUnknown) {
+ *ppvObject = static_cast<IUnknown*>(this);
+ } else if (riid == IID_IMFAsyncCallback) {
+ *ppvObject = static_cast<IMFAsyncCallback*>(this);
+ } else {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+ AddRef();
+ return S_OK;
+}
+
+STDMETHODIMP_(ULONG) SourceResolver::AddRef(void)
+{
+ return InterlockedIncrement(&m_cRef);
+}
+
+STDMETHODIMP_(ULONG) SourceResolver::Release(void)
+{
+ LONG cRef = InterlockedDecrement(&m_cRef);
+ if (cRef == 0)
+ this->deleteLater();
+ return cRef;
+}
+
+HRESULT STDMETHODCALLTYPE SourceResolver::Invoke(IMFAsyncResult *pAsyncResult)
+{
+ QMutexLocker locker(&m_mutex);
+
+ if (!m_sourceResolver)
+ return S_OK;
+
+ MF_OBJECT_TYPE ObjectType = MF_OBJECT_INVALID;
+ IUnknown* pSource = NULL;
+ State *state = static_cast<State*>(pAsyncResult->GetStateNoAddRef());
+
+ HRESULT hr = S_OK;
+ if (state->fromStream())
+ hr = m_sourceResolver->EndCreateObjectFromByteStream(pAsyncResult, &ObjectType, &pSource);
+ else
+ hr = m_sourceResolver->EndCreateObjectFromURL(pAsyncResult, &ObjectType, &pSource);
+
+ if (state->sourceResolver() != m_sourceResolver) {
+ //This is a cancelled one
+ return S_OK;
+ }
+
+ if (m_cancelCookie) {
+ m_cancelCookie->Release();
+ m_cancelCookie = NULL;
+ }
+
+ if (FAILED(hr)) {
+ emit error(hr);
+ return S_OK;
+ }
+
+ if (m_mediaSource) {
+ m_mediaSource->Release();
+ m_mediaSource = NULL;
+ }
+
+ hr = pSource->QueryInterface(IID_PPV_ARGS(&m_mediaSource));
+ pSource->Release();
+ if (FAILED(hr)) {
+ emit error(hr);
+ return S_OK;
+ }
+
+ emit mediaSourceReady();
+
+ return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE SourceResolver::GetParameters(DWORD*, DWORD*)
+{
+ return E_NOTIMPL;
+}
+
+void SourceResolver::load(const QUrl &url, QIODevice* stream)
+{
+ QMutexLocker locker(&m_mutex);
+ HRESULT hr = S_OK;
+ if (!m_sourceResolver)
+ hr = MFCreateSourceResolver(&m_sourceResolver);
+
+ if (m_stream) {
+ m_stream->Release();
+ m_stream = NULL;
+ }
+
+ if (FAILED(hr)) {
+ qWarning() << "Failed to create Source Resolver!";
+ emit error(hr);
+ } else if (stream) {
+ QString urlString = url.toString();
+ m_stream = new MFStream(stream, false);
+ hr = m_sourceResolver->BeginCreateObjectFromByteStream(
+ m_stream, urlString.isEmpty() ? 0 : reinterpret_cast<LPCWSTR>(urlString.utf16()),
+ MF_RESOLUTION_MEDIASOURCE | MF_RESOLUTION_CONTENT_DOES_NOT_HAVE_TO_MATCH_EXTENSION_OR_MIME_TYPE
+ , NULL, &m_cancelCookie, this, new State(m_sourceResolver, true));
+ if (FAILED(hr)) {
+ qWarning() << "Unsupported stream!";
+ emit error(hr);
+ }
+ } else {
+#ifdef DEBUG_MEDIAFOUNDATION
+ qDebug() << "loading :" << url;
+ qDebug() << "url path =" << url.path().mid(1);
+#endif
+#ifdef TEST_STREAMING
+ //Testing stream function
+ if (url.scheme() == QLatin1String("file")) {
+ stream = new QFile(url.path().mid(1));
+ if (stream->open(QIODevice::ReadOnly)) {
+ m_stream = new MFStream(stream, true);
+ hr = m_sourceResolver->BeginCreateObjectFromByteStream(
+ m_stream, reinterpret_cast<const OLECHAR *>(url.toString().utf16()),
+ MF_RESOLUTION_MEDIASOURCE | MF_RESOLUTION_CONTENT_DOES_NOT_HAVE_TO_MATCH_EXTENSION_OR_MIME_TYPE,
+ NULL, &m_cancelCookie, this, new State(m_sourceResolver, true));
+ if (FAILED(hr)) {
+ qWarning() << "Unsupported stream!";
+ emit error(hr);
+ }
+ } else {
+ delete stream;
+ emit error(QMediaPlayer::FormatError);
+ }
+ } else
+#endif
+ if (url.scheme() == QLatin1String("qrc")) {
+ // If the canonical URL refers to a Qt resource, open with QFile and use
+ // the stream playback capability to play.
+ stream = new QFile(QLatin1Char(':') + url.path());
+ if (stream->open(QIODevice::ReadOnly)) {
+ m_stream = new MFStream(stream, true);
+ hr = m_sourceResolver->BeginCreateObjectFromByteStream(
+ m_stream, reinterpret_cast<const OLECHAR *>(url.toString().utf16()),
+ MF_RESOLUTION_MEDIASOURCE | MF_RESOLUTION_CONTENT_DOES_NOT_HAVE_TO_MATCH_EXTENSION_OR_MIME_TYPE,
+ NULL, &m_cancelCookie, this, new State(m_sourceResolver, true));
+ if (FAILED(hr)) {
+ qWarning() << "Unsupported stream!";
+ emit error(hr);
+ }
+ } else {
+ delete stream;
+ emit error(QMediaPlayer::FormatError);
+ }
+ } else {
+ hr = m_sourceResolver->BeginCreateObjectFromURL(
+ reinterpret_cast<const OLECHAR *>(url.toString().utf16()),
+ MF_RESOLUTION_MEDIASOURCE | MF_RESOLUTION_CONTENT_DOES_NOT_HAVE_TO_MATCH_EXTENSION_OR_MIME_TYPE,
+ NULL, &m_cancelCookie, this, new State(m_sourceResolver, false));
+ if (FAILED(hr)) {
+ qWarning() << "Unsupported url scheme!";
+ emit error(hr);
+ }
+ }
+ }
+}
+
+void SourceResolver::cancel()
+{
+ QMutexLocker locker(&m_mutex);
+ if (m_cancelCookie) {
+ m_sourceResolver->CancelObjectCreation(m_cancelCookie);
+ m_cancelCookie->Release();
+ m_cancelCookie = NULL;
+ m_sourceResolver->Release();
+ m_sourceResolver = NULL;
+ }
+}
+
+void SourceResolver::shutdown()
+{
+ if (m_mediaSource) {
+ m_mediaSource->Shutdown();
+ m_mediaSource->Release();
+ m_mediaSource = NULL;
+ }
+
+ if (m_stream) {
+ m_stream->Release();
+ m_stream = NULL;
+ }
+}
+
+IMFMediaSource* SourceResolver::mediaSource() const
+{
+ return m_mediaSource;
+}
+
+/////////////////////////////////////////////////////////////////////////////////
+SourceResolver::State::State(IMFSourceResolver *sourceResolver, bool fromStream)
+ : m_cRef(0)
+ , m_sourceResolver(sourceResolver)
+ , m_fromStream(fromStream)
+{
+ sourceResolver->AddRef();
+}
+
+SourceResolver::State::~State()
+{
+ m_sourceResolver->Release();
+}
+
+STDMETHODIMP SourceResolver::State::QueryInterface(REFIID riid, LPVOID *ppvObject)
+{
+ if (!ppvObject)
+ return E_POINTER;
+ if (riid == IID_IUnknown) {
+ *ppvObject = static_cast<IUnknown*>(this);
+ } else {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+ AddRef();
+ return S_OK;
+}
+
+STDMETHODIMP_(ULONG) SourceResolver::State::AddRef(void)
+{
+ return InterlockedIncrement(&m_cRef);
+}
+
+STDMETHODIMP_(ULONG) SourceResolver::State::Release(void)
+{
+ LONG cRef = InterlockedDecrement(&m_cRef);
+ if (cRef == 0)
+ delete this;
+ // For thread safety, return a temporary variable.
+ return cRef;
+}
+
+IMFSourceResolver* SourceResolver::State::sourceResolver() const
+{
+ return m_sourceResolver;
+}
+
+bool SourceResolver::State::fromStream() const
+{
+ return m_fromStream;
+}
+
diff --git a/src/multimedia/platform/wmf/sourceresolver_p.h b/src/multimedia/platform/wmf/sourceresolver_p.h
new file mode 100644
index 000000000..07da1d8bd
--- /dev/null
+++ b/src/multimedia/platform/wmf/sourceresolver_p.h
@@ -0,0 +1,115 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $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$
+**
+****************************************************************************/
+
+#ifndef SOURCERESOLVER_H
+#define SOURCERESOLVER_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "mfstream_p.h"
+#include <QUrl>
+
+class SourceResolver: public QObject, public IMFAsyncCallback
+{
+ Q_OBJECT
+public:
+ SourceResolver();
+
+ ~SourceResolver();
+
+ STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject);
+ STDMETHODIMP_(ULONG) AddRef(void);
+ STDMETHODIMP_(ULONG) Release(void);
+
+ HRESULT STDMETHODCALLTYPE Invoke(IMFAsyncResult *pAsyncResult);
+
+ HRESULT STDMETHODCALLTYPE GetParameters(DWORD*, DWORD*);
+
+ void load(const QUrl &url, QIODevice* stream);
+
+ void cancel();
+
+ void shutdown();
+
+ IMFMediaSource* mediaSource() const;
+
+Q_SIGNALS:
+ void error(long hr);
+ void mediaSourceReady();
+
+private:
+ class State : public IUnknown
+ {
+ public:
+ State(IMFSourceResolver *sourceResolver, bool fromStream);
+ ~State();
+
+ STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject);
+
+ STDMETHODIMP_(ULONG) AddRef(void);
+
+ STDMETHODIMP_(ULONG) Release(void);
+
+ IMFSourceResolver* sourceResolver() const;
+ bool fromStream() const;
+
+ private:
+ long m_cRef;
+ IMFSourceResolver *m_sourceResolver;
+ bool m_fromStream;
+ };
+
+ long m_cRef;
+ IUnknown *m_cancelCookie;
+ IMFSourceResolver *m_sourceResolver;
+ IMFMediaSource *m_mediaSource;
+ MFStream *m_stream;
+ QMutex m_mutex;
+};
+
+#endif
diff --git a/src/multimedia/platform/wmf/wmf.pri b/src/multimedia/platform/wmf/wmf.pri
new file mode 100644
index 000000000..81d63c3bf
--- /dev/null
+++ b/src/multimedia/platform/wmf/wmf.pri
@@ -0,0 +1,21 @@
+QT += network
+
+win32:!qtHaveModule(opengl) {
+ LIBS_PRIVATE += -lgdi32 -luser32
+}
+
+INCLUDEPATH += .
+
+HEADERS += \
+ $$PWD/wmfserviceplugin_p.h \
+ $$PWD/mfstream_p.h \
+ $$PWD/sourceresolver_p.h
+
+SOURCES += \
+ $$PWD/wmfserviceplugin.cpp \
+ $$PWD/mfstream.cpp \
+ $$PWD/sourceresolver.cpp
+
+include (evr/evr.pri)
+include (player/player.pri)
+include (decoder/decoder.pri)
diff --git a/src/multimedia/platform/wmf/wmfserviceplugin.cpp b/src/multimedia/platform/wmf/wmfserviceplugin.cpp
new file mode 100644
index 000000000..0eb20e482
--- /dev/null
+++ b/src/multimedia/platform/wmf/wmfserviceplugin.cpp
@@ -0,0 +1,103 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $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 <QtCore/qstring.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/QFile>
+
+#include "wmfserviceplugin_p.h"
+#include "mfplayerservice_p.h"
+
+#include <mfapi.h>
+
+namespace
+{
+static int g_refCount = 0;
+void addRefCount()
+{
+ g_refCount++;
+ if (g_refCount == 1) {
+ CoInitialize(NULL);
+ MFStartup(MF_VERSION);
+ }
+}
+
+void releaseRefCount()
+{
+ g_refCount--;
+ if (g_refCount == 0) {
+ MFShutdown();
+ CoUninitialize();
+ }
+}
+
+}
+
+QMediaService* WMFServicePlugin::create(QString const& key)
+{
+ if (key == QLatin1String(Q_MEDIASERVICE_MEDIAPLAYER)) {
+ addRefCount();
+ return new MFPlayerService;
+ }
+
+ //qDebug() << "unsupported key:" << key;
+ return 0;
+}
+
+void WMFServicePlugin::release(QMediaService *service)
+{
+ delete service;
+ releaseRefCount();
+}
+
+QByteArray WMFServicePlugin::defaultDevice(const QByteArray &) const
+{
+ return QByteArray();
+}
+
+QList<QByteArray> WMFServicePlugin::devices(const QByteArray &) const
+{
+ return QList<QByteArray>();
+}
+
+QString WMFServicePlugin::deviceDescription(const QByteArray &, const QByteArray &)
+{
+ return QString();
+}
+
diff --git a/src/multimedia/platform/wmf/wmfserviceplugin_p.h b/src/multimedia/platform/wmf/wmfserviceplugin_p.h
new file mode 100644
index 000000000..ef769b22a
--- /dev/null
+++ b/src/multimedia/platform/wmf/wmfserviceplugin_p.h
@@ -0,0 +1,76 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $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$
+**
+****************************************************************************/
+
+#ifndef WMFSERVICEPLUGIN_H
+#define WMFSERVICEPLUGIN_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtMultimedia/private/qtmultimediaglobal_p.h>
+#include "qmediaserviceproviderplugin.h"
+
+QT_USE_NAMESPACE
+
+class WMFServicePlugin
+ : public QMediaServiceProviderPlugin
+ , public QMediaServiceSupportedDevicesInterface
+{
+ Q_OBJECT
+ Q_INTERFACES(QMediaServiceSupportedDevicesInterface)
+ Q_PLUGIN_METADATA(IID "org.qt-project.qt.mediaserviceproviderfactory/5.0" FILE "wmf.json")
+
+public:
+ QMediaService* create(QString const& key);
+ void release(QMediaService *service);
+
+ QByteArray defaultDevice(const QByteArray &service) const;
+ QList<QByteArray> devices(const QByteArray &service) const;
+ QString deviceDescription(const QByteArray &service, const QByteArray &device);
+};
+
+#endif // WMFSERVICEPLUGIN_H