diff options
Diffstat (limited to 'src/multimedia/platform/wmf/player')
25 files changed, 8030 insertions, 0 deletions
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 |