diff options
Diffstat (limited to 'src/multimedia/platform/wmf')
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(¶ms); + 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, ¤tTime, &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 |