From 1e4dda971098656dad390478601ee932e9c4e3e3 Mon Sep 17 00:00:00 2001 From: Michael Goddard Date: Mon, 25 Jul 2011 16:45:09 +1000 Subject: Add Windows Media Foundation backend for QMediaPlayer Task-number:QTMOBILITY-1606 Reviewed-by: Michael Goddard (cherry picked from commit bdf3a9b39661cfb836245139d02e95e854646a7e) (cherry picked from commit 8d2370953eb32bf44a037244e0d9f9b484875f7a) Change-Id: I07790b8c540a04e4e19a3d018a4884a773e980bf Reviewed-on: http://codereview.qt.nokia.com/2086 Reviewed-by: Qt Sanity Bot Reviewed-by: Ling Hu --- config.tests/wmf/main.cpp | 52 + config.tests/wmf/wmf.pro | 10 + src/plugins/directshow/directshow.pro | 2 +- src/plugins/directshow/dsserviceplugin.cpp | 1 + src/plugins/plugins.pro | 1 + src/plugins/wmf/player/evr9videowindowcontrol.cpp | 351 ++++ src/plugins/wmf/player/evr9videowindowcontrol.h | 111 ++ src/plugins/wmf/player/mfaudioendpointcontrol.cpp | 162 ++ src/plugins/wmf/player/mfaudioendpointcontrol.h | 84 + src/plugins/wmf/player/mfmetadatacontrol.cpp | 246 +++ src/plugins/wmf/player/mfmetadatacontrol.h | 80 + src/plugins/wmf/player/mfplayercontrol.cpp | 311 +++ src/plugins/wmf/player/mfplayercontrol.h | 124 ++ src/plugins/wmf/player/mfplayerservice.cpp | 154 ++ src/plugins/wmf/player/mfplayerservice.h | 96 + src/plugins/wmf/player/mfplayersession.cpp | 1388 +++++++++++++ src/plugins/wmf/player/mfplayersession.h | 212 ++ src/plugins/wmf/player/mfstream.cpp | 361 ++++ src/plugins/wmf/player/mfstream.h | 149 ++ src/plugins/wmf/player/mfvideorenderercontrol.cpp | 2201 +++++++++++++++++++++ src/plugins/wmf/player/mfvideorenderercontrol.h | 76 + src/plugins/wmf/player/player.pri | 30 + src/plugins/wmf/player/sourceresolver.cpp | 318 +++ src/plugins/wmf/player/sourceresolver.h | 106 + src/plugins/wmf/wmf.pro | 16 + src/plugins/wmf/wmfserviceplugin.cpp | 97 + src/plugins/wmf/wmfserviceplugin.h | 68 + sync.profile | 1 + 28 files changed, 6807 insertions(+), 1 deletion(-) create mode 100644 config.tests/wmf/main.cpp create mode 100644 config.tests/wmf/wmf.pro create mode 100644 src/plugins/wmf/player/evr9videowindowcontrol.cpp create mode 100644 src/plugins/wmf/player/evr9videowindowcontrol.h create mode 100644 src/plugins/wmf/player/mfaudioendpointcontrol.cpp create mode 100644 src/plugins/wmf/player/mfaudioendpointcontrol.h create mode 100644 src/plugins/wmf/player/mfmetadatacontrol.cpp create mode 100644 src/plugins/wmf/player/mfmetadatacontrol.h create mode 100644 src/plugins/wmf/player/mfplayercontrol.cpp create mode 100644 src/plugins/wmf/player/mfplayercontrol.h create mode 100644 src/plugins/wmf/player/mfplayerservice.cpp create mode 100644 src/plugins/wmf/player/mfplayerservice.h create mode 100644 src/plugins/wmf/player/mfplayersession.cpp create mode 100644 src/plugins/wmf/player/mfplayersession.h create mode 100644 src/plugins/wmf/player/mfstream.cpp create mode 100644 src/plugins/wmf/player/mfstream.h create mode 100644 src/plugins/wmf/player/mfvideorenderercontrol.cpp create mode 100644 src/plugins/wmf/player/mfvideorenderercontrol.h create mode 100644 src/plugins/wmf/player/player.pri create mode 100644 src/plugins/wmf/player/sourceresolver.cpp create mode 100644 src/plugins/wmf/player/sourceresolver.h create mode 100644 src/plugins/wmf/wmf.pro create mode 100644 src/plugins/wmf/wmfserviceplugin.cpp create mode 100644 src/plugins/wmf/wmfserviceplugin.h diff --git a/config.tests/wmf/main.cpp b/config.tests/wmf/main.cpp new file mode 100644 index 000000000..db361ca64 --- /dev/null +++ b/config.tests/wmf/main.cpp @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +int main(int, char**) +{ + return 0; +} diff --git a/config.tests/wmf/wmf.pro b/config.tests/wmf/wmf.pro new file mode 100644 index 000000000..31b532843 --- /dev/null +++ b/config.tests/wmf/wmf.pro @@ -0,0 +1,10 @@ +CONFIG -= qt +CONFIG += console +TEMPLATE = app + +# Input +SOURCES += main.cpp + +LIBS += -lstrmiids -ldmoguids -luuid -lmsdmo -lole32 -loleaut32 -lMf -lMfuuid -lMfplat -lPropsys + + diff --git a/src/plugins/directshow/directshow.pro b/src/plugins/directshow/directshow.pro index 513f810fb..e2a37e612 100644 --- a/src/plugins/directshow/directshow.pro +++ b/src/plugins/directshow/directshow.pro @@ -19,7 +19,7 @@ SOURCES += dsserviceplugin.cpp !contains(config_test_wmsdk, yes): DEFINES += QT_NO_WMSDK -include (player/player.pri) +contains(config_test_wmf, no): include (player/player.pri) include (camera/camera.pri) target.path += $$[QT_INSTALL_PLUGINS]/$${PLUGIN_TYPE} diff --git a/src/plugins/directshow/dsserviceplugin.cpp b/src/plugins/directshow/dsserviceplugin.cpp index b354d2195..adfb025d8 100644 --- a/src/plugins/directshow/dsserviceplugin.cpp +++ b/src/plugins/directshow/dsserviceplugin.cpp @@ -47,6 +47,7 @@ #ifdef QMEDIA_DIRECTSHOW_CAMERA +#include #include "dscameraservice.h" #endif diff --git a/src/plugins/plugins.pro b/src/plugins/plugins.pro index bf747612f..6ae546f72 100644 --- a/src/plugins/plugins.pro +++ b/src/plugins/plugins.pro @@ -14,6 +14,7 @@ win32 { win32 { contains(config_test_directshow, yes): SUBDIRS += directshow + contains(config_test_wmf, yes) : SUBDIRS += wmf } simulator: SUBDIRS += simulator diff --git a/src/plugins/wmf/player/evr9videowindowcontrol.cpp b/src/plugins/wmf/player/evr9videowindowcontrol.cpp new file mode 100644 index 000000000..0b705b743 --- /dev/null +++ b/src/plugins/wmf/player/evr9videowindowcontrol.cpp @@ -0,0 +1,351 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "evr9videowindowcontrol.h" +#include +#include + +Evr9VideoWindowControl::Evr9VideoWindowControl(QObject *parent) + : QVideoWindowControl(parent) + , m_windowId(0) + , m_dirtyValues(0) + , m_aspectRatioMode(Qt::KeepAspectRatio) + , m_brightness(0) + , m_contrast(0) + , m_hue(0) + , m_saturation(0) + , m_fullScreen(false) + , m_currentActivate(0) + , m_evrSink(0) + , m_displayControl(0) +{ + if (FAILED(MFCreateVideoRendererActivate(0, &m_currentActivate))) { + qWarning() << "Failed to create evr video renderer activate!"; + return; + } + if (FAILED(m_currentActivate->ActivateObject(IID_IMFMediaSink, (LPVOID*)(&m_evrSink)))) { + qWarning() << "Failed to activate evr media sink!"; + return; + } + if (FAILED(MFGetService(m_evrSink, MR_VIDEO_RENDER_SERVICE, IID_PPV_ARGS(&m_displayControl)))) { + qWarning() << "Failed to get display control from evr media sink!"; + return; + } + if (FAILED(MFGetService(m_evrSink, MR_VIDEO_MIXER_SERVICE, IID_PPV_ARGS(&m_processor)))) { + qWarning() << "Failed to get video processor from evr media sink!"; + return; + } +} + +Evr9VideoWindowControl::~Evr9VideoWindowControl() +{ + if (m_processor) + m_processor->Release(); + if (m_displayControl) + m_displayControl->Release(); + if (m_evrSink) + m_evrSink->Release(); + if (m_currentActivate) { + m_currentActivate->ShutdownObject(); + m_currentActivate->Release(); + } +} + + +WId Evr9VideoWindowControl::winId() const +{ + return m_windowId; +} + +void Evr9VideoWindowControl::setWinId(WId id) +{ + m_windowId = id; + + if (QWidget *widget = QWidget::find(m_windowId)) { + const QColor color = widget->palette().color(QPalette::Window); + + m_windowColor = RGB(color.red(), color.green(), color.blue()); + } + + if (m_displayControl) { + m_displayControl->SetVideoWindow(m_windowId); + } +} + +QRect Evr9VideoWindowControl::displayRect() const +{ + return m_displayRect; +} + +void Evr9VideoWindowControl::setDisplayRect(const QRect &rect) +{ + m_displayRect = rect; + + if (m_displayControl) { + RECT displayRect = { rect.left(), rect.top(), rect.right() + 1, rect.bottom() + 1 }; + QSize sourceSize = nativeSize(); + + RECT sourceRect = { 0, 0, sourceSize.width(), sourceSize.height() }; + + if (m_aspectRatioMode == Qt::KeepAspectRatioByExpanding) { + QSize clippedSize = rect.size(); + clippedSize.scale(sourceRect.right, sourceRect.bottom, Qt::KeepAspectRatio); + + sourceRect.left = (sourceRect.right - clippedSize.width()) / 2; + sourceRect.top = (sourceRect.bottom - clippedSize.height()) / 2; + sourceRect.right = sourceRect.left + clippedSize.width(); + sourceRect.bottom = sourceRect.top + clippedSize.height(); + } + + if (sourceSize.width() > 0 && sourceSize.height() > 0) { + MFVideoNormalizedRect sourceNormRect; + sourceNormRect.left = float(sourceRect.left) / float(sourceRect.right); + sourceNormRect.top = float(sourceRect.top) / float(sourceRect.bottom); + sourceNormRect.right = float(sourceRect.right) / float(sourceRect.right); + sourceNormRect.bottom = float(sourceRect.bottom) / float(sourceRect.bottom); + m_displayControl->SetVideoPosition(&sourceNormRect, &displayRect); + } else { + m_displayControl->SetVideoPosition(NULL, &displayRect); + } + } +} + +bool Evr9VideoWindowControl::isFullScreen() const +{ + return m_fullScreen; +} + +void Evr9VideoWindowControl::setFullScreen(bool fullScreen) +{ + if (m_fullScreen == fullScreen) + return; + emit fullScreenChanged(m_fullScreen = fullScreen); +} + +void Evr9VideoWindowControl::repaint() +{ + QSize size = nativeSize(); + if (size.width() > 0 && size.height() > 0 + && m_displayControl + && SUCCEEDED(m_displayControl->RepaintVideo())) { + return; + } + + PAINTSTRUCT paint; + if (HDC dc = ::BeginPaint(m_windowId, &paint)) { + HPEN pen = ::CreatePen(PS_SOLID, 1, m_windowColor); + HBRUSH brush = ::CreateSolidBrush(m_windowColor); + ::SelectObject(dc, pen); + ::SelectObject(dc, brush); + + ::Rectangle( + dc, + m_displayRect.left(), + m_displayRect.top(), + m_displayRect.right() + 1, + m_displayRect.bottom() + 1); + + ::DeleteObject(pen); + ::DeleteObject(brush); + ::EndPaint(m_windowId, &paint); + } +} + +QSize Evr9VideoWindowControl::nativeSize() const +{ + QSize size; + if (m_displayControl) { + SIZE sourceSize; + if (SUCCEEDED(m_displayControl->GetNativeVideoSize(&sourceSize, 0))) + size = QSize(sourceSize.cx, sourceSize.cy); + } + return size; +} + +Qt::AspectRatioMode Evr9VideoWindowControl::aspectRatioMode() const +{ + return m_aspectRatioMode; +} + +void Evr9VideoWindowControl::setAspectRatioMode(Qt::AspectRatioMode mode) +{ + m_aspectRatioMode = mode; + + if (m_displayControl) { + switch (mode) { + case Qt::IgnoreAspectRatio: + //comment from MSDN: Do not maintain the aspect ratio of the video. Stretch the video to fit the output rectangle. + m_displayControl->SetAspectRatioMode(MFVideoARMode_None); + break; + case Qt::KeepAspectRatio: + //comment from MSDN: Preserve the aspect ratio of the video by letterboxing or within the output rectangle. + m_displayControl->SetAspectRatioMode(MFVideoARMode_PreservePicture); + break; + case Qt::KeepAspectRatioByExpanding: + //for this mode, more adjustment will be done in setDisplayRect + m_displayControl->SetAspectRatioMode(MFVideoARMode_PreservePicture); + break; + default: + break; + } + setDisplayRect(m_displayRect); + } +} + +int Evr9VideoWindowControl::brightness() const +{ + return m_brightness; +} + +void Evr9VideoWindowControl::setBrightness(int brightness) +{ + if (m_brightness == brightness) + return; + + m_brightness = brightness; + + m_dirtyValues |= DXVA2_ProcAmp_Brightness; + + setProcAmpValues(); + + emit brightnessChanged(brightness); +} + +int Evr9VideoWindowControl::contrast() const +{ + return m_contrast; +} + +void Evr9VideoWindowControl::setContrast(int contrast) +{ + if (m_contrast == contrast) + return; + + m_contrast = contrast; + + m_dirtyValues |= DXVA2_ProcAmp_Contrast; + + setProcAmpValues(); + + emit contrastChanged(contrast); +} + +int Evr9VideoWindowControl::hue() const +{ + return m_hue; +} + +void Evr9VideoWindowControl::setHue(int hue) +{ + if (m_hue == hue) + return; + + m_hue = hue; + + m_dirtyValues |= DXVA2_ProcAmp_Hue; + + setProcAmpValues(); + + emit hueChanged(hue); +} + +int Evr9VideoWindowControl::saturation() const +{ + return m_saturation; +} + +void Evr9VideoWindowControl::setSaturation(int saturation) +{ + if (m_saturation == saturation) + return; + + m_saturation = saturation; + + m_dirtyValues |= DXVA2_ProcAmp_Saturation; + + setProcAmpValues(); + + emit saturationChanged(saturation); +} + +IMFActivate* Evr9VideoWindowControl::currentActivate() const +{ + return m_currentActivate; +} + +void Evr9VideoWindowControl::setProcAmpValues() +{ + if (m_processor) { + DXVA2_ProcAmpValues values; + if (m_dirtyValues & DXVA2_ProcAmp_Brightness) { + values.Brightness = scaleProcAmpValue(DXVA2_ProcAmp_Brightness, m_brightness); + } + if (m_dirtyValues & DXVA2_ProcAmp_Contrast) { + values.Contrast = scaleProcAmpValue(DXVA2_ProcAmp_Contrast, m_contrast); + } + if (m_dirtyValues & DXVA2_ProcAmp_Hue) { + values.Hue = scaleProcAmpValue(DXVA2_ProcAmp_Hue, m_hue); + } + if (m_dirtyValues & DXVA2_ProcAmp_Saturation) { + values.Saturation = scaleProcAmpValue(DXVA2_ProcAmp_Saturation, m_saturation); + } + + if (SUCCEEDED(m_processor->SetProcAmpValues(0, &values))) { + m_dirtyValues = 0; + } + } +} + +DXVA2_Fixed32 Evr9VideoWindowControl::scaleProcAmpValue(DWORD prop, int value) const +{ + float scaledValue = 0.0; + + DXVA2_ValueRange range; + if (SUCCEEDED(m_processor->GetProcAmpRange(prop, &range))) { + scaledValue = DXVA2FixedToFloat(range.DefaultValue); + if (value > 0) + scaledValue += float(value) * (DXVA2FixedToFloat(range.MaxValue) - DXVA2FixedToFloat(range.DefaultValue)) / 100; + else if (value < 0) + scaledValue -= float(value) * (DXVA2FixedToFloat(range.MinValue) - DXVA2FixedToFloat(range.DefaultValue)) / 100; + } + + return DXVA2FloatToFixed(scaledValue); +} diff --git a/src/plugins/wmf/player/evr9videowindowcontrol.h b/src/plugins/wmf/player/evr9videowindowcontrol.h new file mode 100644 index 000000000..a39d75041 --- /dev/null +++ b/src/plugins/wmf/player/evr9videowindowcontrol.h @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef EVR9VIDEOWINDOWCONTROL_H +#define EVR9VIDEOWINDOWCONTROL_H + +#include "../../src/multimedia/qvideowindowcontrol.h" + +#include +#include +#include + +QT_USE_NAMESPACE + +class Evr9VideoWindowControl : public QVideoWindowControl +{ + Q_OBJECT +public: + Evr9VideoWindowControl(QObject *parent = 0); + ~Evr9VideoWindowControl(); + + WId winId() const; + void setWinId(WId id); + + QRect displayRect() const; + void setDisplayRect(const QRect &rect); + + bool isFullScreen() const; + void setFullScreen(bool fullScreen); + + void repaint(); + + QSize nativeSize() const; + + Qt::AspectRatioMode aspectRatioMode() const; + void setAspectRatioMode(Qt::AspectRatioMode mode); + + int brightness() const; + void setBrightness(int brightness); + + int contrast() const; + void setContrast(int contrast); + + int hue() const; + void setHue(int hue); + + int saturation() const; + void setSaturation(int saturation); + + IMFActivate* currentActivate() const; + +private: + void setProcAmpValues(); + DXVA2_Fixed32 scaleProcAmpValue(DWORD prop, int value) const; + + WId m_windowId; + COLORREF m_windowColor; + DWORD m_dirtyValues; + Qt::AspectRatioMode m_aspectRatioMode; + QRect m_displayRect; + int m_brightness; + int m_contrast; + int m_hue; + int m_saturation; + bool m_fullScreen; + + IMFActivate *m_currentActivate; + IMFMediaSink *m_evrSink; + IMFVideoDisplayControl *m_displayControl; + IMFVideoProcessor *m_processor; +}; + +#endif diff --git a/src/plugins/wmf/player/mfaudioendpointcontrol.cpp b/src/plugins/wmf/player/mfaudioendpointcontrol.cpp new file mode 100644 index 000000000..dac5a0abf --- /dev/null +++ b/src/plugins/wmf/player/mfaudioendpointcontrol.cpp @@ -0,0 +1,162 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "QtCore/qdebug.h" +#include "mfaudioendpointcontrol.h" + +MFAudioEndpointControl::MFAudioEndpointControl(QObject *parent) + : QAudioEndpointSelector(parent) + , m_currentActivate(0) +{ + updateEndpoints(); + setActiveEndpoint(m_defaultEndpoint); +} + +MFAudioEndpointControl::~MFAudioEndpointControl() +{ + foreach (LPWSTR wstrID, m_devices) + CoTaskMemFree(wstrID); + + if (m_currentActivate) + m_currentActivate->Release(); +} + +QList MFAudioEndpointControl::availableEndpoints() const +{ + return m_devices.keys(); +} + +QString MFAudioEndpointControl::endpointDescription(const QString &name) const +{ + return name.section(QLatin1Char('\\'), -1); +} + +QString MFAudioEndpointControl::defaultEndpoint() const +{ + return m_defaultEndpoint; +} + +QString MFAudioEndpointControl::activeEndpoint() const +{ + return m_activeEndpoint; +} + +void MFAudioEndpointControl::setActiveEndpoint(const QString &name) +{ + if (m_activeEndpoint == name) + return; + QMap::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 apropriate 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::currentActivate() const +{ + 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/plugins/wmf/player/mfaudioendpointcontrol.h b/src/plugins/wmf/player/mfaudioendpointcontrol.h new file mode 100644 index 000000000..b4aa6f797 --- /dev/null +++ b/src/plugins/wmf/player/mfaudioendpointcontrol.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef MFAUDIOENDPOINTCONTROL_H +#define MFAUDIOENDPOINTCONTROL_H + +#include +#include +#include + +#include "../../src/multimedia/qaudioendpointselector.h" + +class MFPlayerService; + +QT_USE_NAMESPACE + +class MFAudioEndpointControl : public QAudioEndpointSelector +{ + Q_OBJECT +public: + MFAudioEndpointControl(QObject *parent = 0); + ~MFAudioEndpointControl(); + + QList availableEndpoints() const; + + QString endpointDescription(const QString &name) const; + + QString defaultEndpoint() const; + QString activeEndpoint() const; + + void setActiveEndpoint(const QString& name); + + IMFActivate* currentActivate() const; + +private: + void updateEndpoints(); + + QString m_defaultEndpoint; + QString m_activeEndpoint; + QMap m_devices; + IMFActivate *m_currentActivate; + +}; + +#endif + diff --git a/src/plugins/wmf/player/mfmetadatacontrol.cpp b/src/plugins/wmf/player/mfmetadatacontrol.cpp new file mode 100644 index 000000000..21cc7032b --- /dev/null +++ b/src/plugins/wmf/player/mfmetadatacontrol.cpp @@ -0,0 +1,246 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "mfmetadatacontrol.h" +#include "mfplayerservice.h" +#include "Propkey.h" + +//#define DEBUG_MEDIAFOUNDATION + +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(QtMultimediaKit::MetaData 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(m_commonNames[index].utf16(), &var); + + if (SUCCEEDED(hr)) + value = convertValue(var); + + PropVariantClear(&var); + return value; +} + +QVariant MFMetaDataControl::convertValue(const PROPVARIANT& var) const +{ + QVariant value; + //form MSDN: http://msdn.microsoft.com/en-us/library/ff384862%28v=VS.85%29.aspx + //it seems that those 4 types are enough for media foundation metadata + //add more later if necessary + switch (var.vt) { + case VT_LPWSTR: + value = QString::fromUtf16(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; + } + return value; +} + +QList MFMetaDataControl::availableMetaData() const +{ + return m_availableMetaDatas; +} + +QVariant MFMetaDataControl::extendedMetaData(const QString &key) const +{ + QVariant value; + HRESULT hr = S_FALSE; + PROPVARIANT var; + PropVariantInit(&var); + if (m_content) { + int index = m_extendedMetaDatas.indexOf(key); + if (index >= 0) { + hr = m_content->GetValue(m_extendedKeys[index], &var); + } + } else if (m_metaData) { + hr = m_metaData->GetProperty(key.utf16(), &var); + } + + if (SUCCEEDED(hr)) + value = convertValue(var); + + PropVariantClear(&var); + return value; +} + +QStringList MFMetaDataControl::availableExtendedMetaData() const +{ + return m_extendedMetaDatas; +} + +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(); + m_extendedMetaDatas.clear(); + m_extendedKeys.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(QtMultimediaKit::Author); + } else if (key == PKEY_Title) { + m_availableMetaDatas.push_back(QtMultimediaKit::Title); + } else if (key == PKEY_ParentalRating) { + m_availableMetaDatas.push_back(QtMultimediaKit::ParentalRating); + } else if (key == PKEY_Comment) { + m_availableMetaDatas.push_back(QtMultimediaKit::Description); + } else if (key == PKEY_Copyright) { + m_availableMetaDatas.push_back(QtMultimediaKit::Copyright); + //TODO: add more common keys + } 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 + bool common = true; + if (wcscmp(sName, L"Author") == 0) { + m_availableMetaDatas.push_back(QtMultimediaKit::Author); + } else if (wcscmp(sName, L"Title") == 0) { + m_availableMetaDatas.push_back(QtMultimediaKit::Title); + } else if (wcscmp(sName, L"Rating") == 0) { + m_availableMetaDatas.push_back(QtMultimediaKit::ParentalRating); + } else if (wcscmp(sName, L"Description") == 0) { + m_availableMetaDatas.push_back(QtMultimediaKit::Description); + } else if (wcscmp(sName, L"Copyright") == 0) { + m_availableMetaDatas.push_back(QtMultimediaKit::Copyright); + //TODO: add more common keys + } else { + common = false; + m_extendedMetaDatas.push_back(QString::fromUtf16(sName)); + } + if (common) + m_commonNames.push_back(QString::fromUtf16(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/plugins/wmf/player/mfmetadatacontrol.h b/src/plugins/wmf/player/mfmetadatacontrol.h new file mode 100644 index 000000000..59eddade7 --- /dev/null +++ b/src/plugins/wmf/player/mfmetadatacontrol.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef MFMETADATACONTROL_H +#define MFMETADATACONTROL_H + +#include +#include "Mfidl.h" + +QT_USE_NAMESPACE + +class MFMetaDataControl : public QMetaDataReaderControl +{ + Q_OBJECT +public: + MFMetaDataControl(QObject *parent = 0); + ~MFMetaDataControl(); + + bool isMetaDataAvailable() const; + + QVariant metaData(QtMultimediaKit::MetaData key) const; + QList availableMetaData() const; + + QVariant extendedMetaData(const QString &key) const; + QStringList availableExtendedMetaData() const; + + void updateSource(IMFPresentationDescriptor* sourcePD, IMFMediaSource* mediaSource); + +private: + QVariant convertValue(const PROPVARIANT& var) const; + IPropertyStore *m_content; //for Windows7 + IMFMetadata *m_metaData; //for Vista + + QList m_availableMetaDatas; + QList m_commonKeys; //for Windows7 + QStringList m_commonNames; //for Vista + + QStringList m_extendedMetaDatas; + QList m_extendedKeys; //for Windows7 +}; + +#endif diff --git a/src/plugins/wmf/player/mfplayercontrol.cpp b/src/plugins/wmf/player/mfplayercontrol.cpp new file mode 100644 index 000000000..3eb0764fb --- /dev/null +++ b/src/plugins/wmf/player/mfplayercontrol.cpp @@ -0,0 +1,311 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "mfplayercontrol.h" +#include + +//#define DEBUG_MEDIAFOUNDATION + +MFPlayerControl::MFPlayerControl(MFPlayerSession *session) +: QMediaPlayerControl(session) +, m_state(QMediaPlayer::StoppedState) +, 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 QMediaContent &media, QIODevice *stream) +{ + stop(); + 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); +} + +QMediaContent 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/plugins/wmf/player/mfplayercontrol.h b/src/plugins/wmf/player/mfplayercontrol.h new file mode 100644 index 000000000..c2399504b --- /dev/null +++ b/src/plugins/wmf/player/mfplayercontrol.h @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef MFPLAYERCONTROL_H +#define MFPLAYERCONTROL_H + +#include "qmediacontent.h" +#include "qmediaplayercontrol.h" + +#include + +#include "mfplayersession.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); + + QMediaContent media() const; + const QIODevice *mediaStream() const; + void setMedia(const QMediaContent &media, QIODevice *stream); + + void play(); + void pause(); + void stop(); + +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; + QMediaContent m_media; + MFPlayerSession *m_session; +}; + +#endif diff --git a/src/plugins/wmf/player/mfplayerservice.cpp b/src/plugins/wmf/player/mfplayerservice.cpp new file mode 100644 index 000000000..81fdf5792 --- /dev/null +++ b/src/plugins/wmf/player/mfplayerservice.cpp @@ -0,0 +1,154 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qmediacontent.h" + +#include + +#include "mfplayercontrol.h" +#ifndef Q_WS_SIMULATOR +#include "evr9videowindowcontrol.h" +#endif +#include "mfvideorenderercontrol.h" +#include "mfaudioendpointcontrol.h" +#include "mfplayerservice.h" +#include "mfplayersession.h" +#include "mfmetadatacontrol.h" + +MFPlayerService::MFPlayerService(QObject *parent) + : QMediaService(parent) + , m_session(0) +#ifndef Q_WS_SIMULATOR + , m_videoWindowControl(0) +#endif + , m_videoRendererControl(0) +{ + CoInitialize(NULL); + MFStartup(MF_VERSION); + m_session = new MFPlayerSession(this); + m_player = new MFPlayerControl(m_session); + m_audioEndpointControl = new MFAudioEndpointControl(this); + m_metaDataControl = new MFMetaDataControl(this); +} + +MFPlayerService::~MFPlayerService() +{ + +#ifndef Q_WS_SIMULATOR + if (m_videoWindowControl) + delete m_videoWindowControl; +#endif + + if (m_videoRendererControl) + delete m_videoRendererControl; + + delete m_session; + + MFShutdown(); + CoUninitialize(); +} + +QMediaControl* MFPlayerService::requestControl(const char *name) +{ + if (qstrcmp(name, QMediaPlayerControl_iid) == 0) { + return m_player; + } else if (qstrcmp(name, QAudioEndpointSelector_iid) == 0) { + return m_audioEndpointControl; + } else if (qstrcmp(name, QMetaDataReaderControl_iid) == 0) { + return m_metaDataControl; + } else if (qstrcmp(name, QVideoRendererControl_iid) == 0) { +#ifndef Q_WS_SIMULATOR + if (!m_videoRendererControl && !m_videoWindowControl) { +#else + if (!m_videoRendererControl) { +#endif + m_videoRendererControl = new MFVideoRendererControl; + return m_videoRendererControl; + } +#ifndef Q_WS_SIMULATOR + } else if (qstrcmp(name, QVideoWindowControl_iid) == 0) { + if (!m_videoRendererControl && !m_videoWindowControl) { + m_videoWindowControl = new Evr9VideoWindowControl; + return m_videoWindowControl; + } +#endif + } + + return 0; +} + +void MFPlayerService::releaseControl(QMediaControl *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; +#ifndef Q_WS_SIMULATOR + } else if (control == m_videoWindowControl) { + delete m_videoWindowControl; + m_videoWindowControl = 0; +#endif + } +} + +MFAudioEndpointControl* MFPlayerService::audioEndpointControl() const +{ + return m_audioEndpointControl; +} + +MFVideoRendererControl* MFPlayerService::videoRendererControl() const +{ + return m_videoRendererControl; +} + +#ifndef Q_WS_SIMULATOR +Evr9VideoWindowControl* MFPlayerService::videoWindowControl() const +{ + return m_videoWindowControl; +} +#endif + +MFMetaDataControl* MFPlayerService::metaDataControl() const +{ + return m_metaDataControl; +} diff --git a/src/plugins/wmf/player/mfplayerservice.h b/src/plugins/wmf/player/mfplayerservice.h new file mode 100644 index 000000000..7fef10784 --- /dev/null +++ b/src/plugins/wmf/player/mfplayerservice.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef MFPLAYERSERVICE_H +#define MFPLAYERSERVICE_H + +#include +#include + +#include "qmediaplayer.h" +#include "qmediaresource.h" +#include "qmediaservice.h" +#include "qmediatimerange.h" + +QT_BEGIN_NAMESPACE +class QMediaContent; +QT_END_NAMESPACE + +QT_USE_NAMESPACE + +#ifndef Q_WS_SIMULATOR +class Evr9VideoWindowControl; +#endif +class MFAudioEndpointControl; +class MFVideoRendererControl; +class MFPlayerControl; +class MFMetaDataControl; +class MFPlayerSession; + +class MFPlayerService : public QMediaService +{ + Q_OBJECT +public: + MFPlayerService(QObject *parent = 0); + ~MFPlayerService(); + + QMediaControl* requestControl(const char *name); + void releaseControl(QMediaControl *control); + + MFAudioEndpointControl* audioEndpointControl() const; + MFVideoRendererControl* videoRendererControl() const; +#ifndef Q_WS_SIMULATOR + Evr9VideoWindowControl* videoWindowControl() const; +#endif + MFMetaDataControl* metaDataControl() const; + +private: + MFPlayerSession *m_session; + MFVideoRendererControl *m_videoRendererControl; + MFAudioEndpointControl *m_audioEndpointControl; +#ifndef Q_WS_SIMULATOR + Evr9VideoWindowControl *m_videoWindowControl; +#endif + MFPlayerControl *m_player; + MFMetaDataControl *m_metaDataControl; +}; + +#endif diff --git a/src/plugins/wmf/player/mfplayersession.cpp b/src/plugins/wmf/player/mfplayersession.cpp new file mode 100644 index 000000000..bb5b4bd2b --- /dev/null +++ b/src/plugins/wmf/player/mfplayersession.cpp @@ -0,0 +1,1388 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qmediacontent.h" +#include "qmediaplayercontrol.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "mfplayercontrol.h" +#ifndef Q_WS_SIMULATOR +#include "evr9videowindowcontrol.h" +#endif +#include "mfvideorenderercontrol.h" +#include "mfaudioendpointcontrol.h" + +#include "mfplayersession.h" +#include "mfplayerservice.h" +#include "mfmetadatacontrol.h" +#include +#include +#include + +//#define DEBUG_MEDIAFOUNDATION +//#define TEST_STREAMING + +namespace +{ + //MFStream is added for supporting QIODevice type of media source. + //It is used to delegate invocations from media foundation(through IMFByteStream) to QIODevice. + class MFStream : public QObject, public IMFByteStream + { + Q_OBJECT + public: + MFStream(QIODevice *stream, bool ownStream) + : m_cRef(1) + , m_stream(stream) + , m_ownStream(ownStream) + , m_currentReadResult(0) + { + //Move to the thread of the stream object + //to make sure invocations on stream + //are happened in the same thread of stream object + this->moveToThread(stream->thread()); + connect(stream, SIGNAL(readyRead()), this, SLOT(handleReadyRead())); + } + + ~MFStream() + { + if (m_currentReadResult) + m_currentReadResult->Release(); + if (m_ownStream) + m_stream->deleteLater(); + } + + //from IUnknown + STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject) + { + if (!ppvObject) + return E_POINTER; + if (riid == IID_IMFByteStream) { + *ppvObject = static_cast(this); + } else if (riid == IID_IUnknown) { + *ppvObject = static_cast(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) { + this->deleteLater(); + } + return cRef; + } + + + //from IMFByteStream + STDMETHODIMP GetCapabilities(DWORD *pdwCapabilities) + { + if (!pdwCapabilities) + return E_INVALIDARG; + *pdwCapabilities = MFBYTESTREAM_IS_READABLE; + if (!m_stream->isSequential()) + *pdwCapabilities |= MFBYTESTREAM_IS_SEEKABLE; + return S_OK; + } + + STDMETHODIMP GetLength(QWORD *pqwLength) + { + if (!pqwLength) + return E_INVALIDARG; + QMutexLocker locker(&m_mutex); + *pqwLength = QWORD(m_stream->size()); + return S_OK; + } + + STDMETHODIMP SetLength(QWORD) + { + return E_NOTIMPL; + } + + STDMETHODIMP GetCurrentPosition(QWORD *pqwPosition) + { + if (!pqwPosition) + return E_INVALIDARG; + QMutexLocker locker(&m_mutex); + *pqwPosition = m_stream->pos(); + return S_OK; + } + + STDMETHODIMP SetCurrentPosition(QWORD qwPosition) + { + QMutexLocker locker(&m_mutex); + //SetCurrentPosition may happend during the BeginRead/EndRead pair, + //refusing to execute SetCurrentPosition during that time seems to be + //the simplest workable solution + if (m_currentReadResult) + return S_FALSE; + + bool seekOK = m_stream->seek(qint64(qwPosition)); + if (seekOK) + return S_OK; + else + return S_FALSE; + } + + STDMETHODIMP IsEndOfStream(BOOL *pfEndOfStream) + { + if (!pfEndOfStream) + return E_INVALIDARG; + QMutexLocker locker(&m_mutex); + *pfEndOfStream = m_stream->atEnd() ? TRUE : FALSE; + return S_OK; + } + + STDMETHODIMP Read(BYTE *pb, ULONG cb, ULONG *pcbRead) + { + QMutexLocker locker(&m_mutex); + qint64 read = m_stream->read((char*)(pb), qint64(cb)); + if (pcbRead) + *pcbRead = ULONG(read); + return S_OK; + } + + STDMETHODIMP BeginRead(BYTE *pb, ULONG cb, IMFAsyncCallback *pCallback, + IUnknown *punkState) + { + if (!pCallback || !pb) + return E_INVALIDARG; + + Q_ASSERT(m_currentReadResult == NULL); + + AsyncReadState *state = new (std::nothrow) AsyncReadState(pb, cb); + if (state == NULL) + return E_OUTOFMEMORY; + + HRESULT hr = MFCreateAsyncResult(state, pCallback, punkState, &m_currentReadResult); + state->Release(); + if (FAILED(hr)) + return hr; + + QCoreApplication::postEvent(this, new QEvent(QEvent::User)); + return hr; + } + + STDMETHODIMP EndRead(IMFAsyncResult* pResult, ULONG *pcbRead) + { + if (!pcbRead) + return E_INVALIDARG; + IUnknown *pUnk; + pResult->GetObject(&pUnk); + AsyncReadState *state = static_cast(pUnk); + *pcbRead = state->bytesRead(); + pUnk->Release(); + + m_currentReadResult->Release(); + m_currentReadResult = NULL; + + return S_OK; + } + + STDMETHODIMP Write(const BYTE *, ULONG, ULONG *) + { + return E_NOTIMPL; + } + + STDMETHODIMP BeginWrite(const BYTE *, ULONG , + IMFAsyncCallback *, + IUnknown *) + { + return E_NOTIMPL; + } + + STDMETHODIMP EndWrite(IMFAsyncResult *, + ULONG *) + { + return E_NOTIMPL; + } + + STDMETHODIMP Seek( + MFBYTESTREAM_SEEK_ORIGIN SeekOrigin, + LONGLONG llSeekOffset, + DWORD, + QWORD *pqwCurrentPosition) + { + QMutexLocker locker(&m_mutex); + if (m_currentReadResult) + return S_FALSE; + + qint64 pos = qint64(llSeekOffset); + switch (SeekOrigin) { + case msoCurrent: + pos += m_stream->pos(); + break; + } + bool seekOK = m_stream->seek(pos); + if (*pqwCurrentPosition) + *pqwCurrentPosition = pos; + if (seekOK) + return S_OK; + else + return S_FALSE; + } + + STDMETHODIMP Flush() + { + return E_NOTIMPL; + } + + STDMETHODIMP Close() + { + QMutexLocker locker(&m_mutex); + if (m_ownStream) + m_stream->close(); + return S_OK; + } + + private: + //AsyncReadState is a helper class used in BeginRead for asynchronous operation + //to record some BeginRead parameters, so these parameters could be + //used later when actually executing the read operation in another thread. + class AsyncReadState : public IUnknown + { + public: + AsyncReadState(BYTE *pb, ULONG cb) + : m_cRef(1) + , m_pb(pb) + , m_cb(cb) + , m_cbRead(0) + { + } + + //from IUnknown + STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject) + { + if (!ppvObject) + return E_POINTER; + + if (riid == IID_IUnknown) { + *ppvObject = static_cast(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; + } + + BYTE* pb() const { return m_pb; } + ULONG cb() const { return m_cb; } + ULONG bytesRead() const { return m_cbRead; } + + void setBytesRead(ULONG cbRead) { m_cbRead = cbRead; } + + private: + long m_cRef; + BYTE *m_pb; + ULONG m_cb; + ULONG m_cbRead; + }; + + long m_cRef; + QIODevice *m_stream; + bool m_ownStream; + DWORD m_workQueueId; + QMutex m_mutex; + + void doRead() + { + bool readDone = true; + IUnknown *pUnk = NULL; + HRESULT hr = m_currentReadResult->GetObject(&pUnk); + if (SUCCEEDED(hr)) { + //do actual read + AsyncReadState *state = static_cast(pUnk); + ULONG cbRead; + Read(state->pb(), state->cb() - state->bytesRead(), &cbRead); + pUnk->Release(); + + state->setBytesRead(cbRead + state->bytesRead()); + if (state->cb() > state->bytesRead() && !m_stream->atEnd()) { + readDone = false; + } + } + + if (readDone) { + //now inform the original caller + m_currentReadResult->SetStatus(hr); + MFInvokeCallback(m_currentReadResult); + } + } + + + private Q_SLOTS: + void handleReadyRead() + { + doRead(); + } + + protected: + void customEvent(QEvent *event) + { + if (event->type() != QEvent::User) { + QObject::customEvent(event); + return; + } + doRead(); + } + IMFAsyncResult *m_currentReadResult; + }; +} + + +MFPlayerSession::MFPlayerSession(QObject *parent) + : QObject(parent) + , m_session(0) + , m_presentationClock(0) + , m_rateControl(0) + , m_rateSupport(0) + , m_volumeControl(0) + , m_netsourceStatistics(0) + , m_hCloseEvent(0) + , m_pendingRate(1) + , m_volume(1) + , m_muted(false) + , m_status(QMediaPlayer::NoMedia) + , m_scrubbing(false) + , m_restoreRate(1) + , m_mediaTypes(0) +{ + m_hCloseEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + m_sourceResolver = new SourceResolver(this); + QObject::connect(m_sourceResolver, SIGNAL(mediaSourceReady()), this, SLOT(handleMediaSourceReady())); + QObject::connect(m_sourceResolver, SIGNAL(error(long)), this, SLOT(handleSourceError(long))); + 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; + + createSession(); + PropVariantInit(&m_varStart); + m_varStart.vt = VT_I8; + m_varStart.uhVal.QuadPart = 0; +} + +MFPlayerSession::~MFPlayerSession() +{ + m_sourceResolver->Release(); + clear(); + HRESULT hr = S_OK; + if (m_session) { + hr = m_session->Close(); + if (SUCCEEDED(hr)) { + DWORD dwWaitResult = WaitForSingleObject(m_hCloseEvent, 5000); + if (dwWaitResult == WAIT_TIMEOUT) { + qWarning() << "session close time out!"; + } + } + } + + if (SUCCEEDED(hr)) { + m_sourceResolver->shutdown(); + if (m_session) + m_session->Shutdown(); + } + + if (m_session) + m_session->Release(); + + CloseHandle(m_hCloseEvent); +} + + +void MFPlayerSession::load(const QMediaContent &media, QIODevice *stream) +{ +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "load"; +#endif + clear(); + QMediaResourceList resources = media.resources(); + + if (m_status == QMediaPlayer::LoadingMedia) + m_sourceResolver->cancel(); + + if (resources.isEmpty() && !stream) { + changeStatus(QMediaPlayer::NoMedia); + } else if (stream && (!stream->isReadable())) { + changeStatus(QMediaPlayer::InvalidMedia); + emit error(QMediaPlayer::ResourceError, tr("Invalid stream source!"), true); + } else { + changeStatus(QMediaPlayer::LoadingMedia); + m_sourceResolver->load(resources, 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; + default: + errorString = tr("Failed to load source."); + break; + } + changeStatus(QMediaPlayer::InvalidMedia); + emit error(errorCode, errorString, true); +} + +void MFPlayerSession::handleMediaSourceReady() +{ + if (QMediaPlayer::LoadingMedia != m_status) + return; +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "handleMediaSourceReady"; +#endif + HRESULT hr = S_OK; + IMFPresentationDescriptor* sourcePD; + IMFMediaSource* mediaSource = m_sourceResolver->mediaSource(); + hr = mediaSource->CreatePresentationDescriptor(&sourcePD); + if (SUCCEEDED(hr)) { + m_duration = 0; + static_cast(this->parent())->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); + } else { + changeStatus(QMediaPlayer::InvalidMedia); + emit error(QMediaPlayer::ResourceError, tr("Can't create presentation descriptor!"), true); + } +} + +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; + } + + // 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; + IMFStreamDescriptor *streamDesc = NULL; + + HRESULT hr = sourcePD->GetStreamDescriptorByIndex(i, &fSelected, &streamDesc); + if (SUCCEEDED(hr)) { + MediaType mediaType = Unknown; + IMFTopologyNode *sourceNode = addSourceNode(topology, source, sourcePD, streamDesc); + if (sourceNode) { + IMFTopologyNode *outputNode = addOutputNode(streamDesc, mediaType, topology, 0); + if (outputNode) { + hr = sourceNode->ConnectOutput(0, outputNode, 0); + if (FAILED(hr)) { + emit error(QMediaPlayer::FormatError, tr("Unable to play some stream"), false); + } + else { + succeededCount++; + m_mediaTypes |= mediaType; + switch (mediaType) { + case Audio: + emit audioAvailable(); + break; + case Video: + emit videoAvailable(); + break; + } + } + } + sourceNode->Release(); + } + streamDesc->Release(); + } + } + + if (succeededCount == 0) { + changeStatus(QMediaPlayer::InvalidMedia); + emit error(QMediaPlayer::ResourceError, tr("Unable to play"), true); + } else { + 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(IMFStreamDescriptor *streamDesc, MediaType& mediaType, IMFTopology* topology, DWORD sinkID) +{ + IMFTopologyNode *node = NULL; + HRESULT hr = MFCreateTopologyNode(MF_TOPOLOGY_OUTPUT_NODE, &node); + if (FAILED(hr)) + return NULL; + node->SetUINT32(MF_TOPONODE_NOSHUTDOWN_ON_REMOVE, FALSE); + + mediaType = Unknown; + IMFMediaTypeHandler *handler = NULL; + hr = streamDesc->GetMediaTypeHandler(&handler); + if (SUCCEEDED(hr)) { + GUID guidMajorType; + hr = handler->GetMajorType(&guidMajorType); + if (SUCCEEDED(hr)) { + IMFActivate *activate = NULL; + MFPlayerService *service = static_cast(this->parent()); + if (MFMediaType_Audio == guidMajorType) { + mediaType = Audio; + activate = service->audioEndpointControl()->currentActivate(); + } else if (MFMediaType_Video == guidMajorType) { + mediaType = Video; + if (service->videoRendererControl()) { + activate = service->videoRendererControl()->currentActivate(); +#ifndef Q_WS_SIMULATOR + } else if (service->videoWindowControl()) { + activate = service->videoWindowControl()->currentActivate(); +#endif + } 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) { + hr = node->SetObject(activate); + if (SUCCEEDED(hr)) { + hr = node->SetUINT32(MF_TOPONODE_STREAMID, sinkID); + if (SUCCEEDED(hr)) { + if (SUCCEEDED(topology->AddNode(node))) + return node; + } + } + } + } + } + node->Release(); + return NULL; +} + +void MFPlayerSession::stop() +{ +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "stop"; +#endif + if (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() +{ + switch (m_status) { + case QMediaPlayer::EndOfMedia: + m_varStart.hVal.QuadPart = 0; + //since it must be loaded already, just fallthrough + case QMediaPlayer::LoadedMedia: + changeStatus(QMediaPlayer::BufferedMedia); + return; + } + +#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() +{ + 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 pulling session events"), false); + } +} + +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); + 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_pendingState != NoPending) + return m_request.rate; + return m_state.rate; +} + +void MFPlayerSession::setPlaybackRate(qreal rate) +{ + if (m_scrubbing) { + m_restoreRate = 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; + } + } + 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; + // 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)) { + // Transition to stopped. + if (cmdNow == CmdStart) { + // Get the current clock position. This will be the restart time. + m_presentationClock->GetCorrelatedTime(0, &hnsClockTime, &hnsSystemTime); + Q_ASSERT(hnsSystemTime != 0); + + // Stop and set the rate + stop(); + + //Cache Request: Restart from stop. + m_request.setCommand(CmdSeekResume); + m_request.start = hnsClockTime / 10000; + } else if (cmdNow == CmdPause) { + // 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. + if (rate < 0 || m_state.rate < 0) { + qWarning() << "Unable to change rate from positive to negative or vice versa in paused state"; + return; + } + } + } 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); + } + } + + // Set the rate. + if (FAILED(m_rateControl->SetRate(isThin, rate))) { + qWarning() << "failed to set playbackrate = " << rate; + return; + } + + // Adjust our current rate and requested rate. + m_pendingRate = m_request.rate = m_state.rate = 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_volumeControl) + m_volumeControl->SetMasterVolume(m_volume * 0.01f); + emit volumeChanged(m_volume); +} + +bool MFPlayerSession::isMuted() const +{ + return m_muted; +} + +void MFPlayerSession::setMuted(bool muted) +{ + if (m_muted == muted) + return; + m_muted = muted; + if (m_volumeControl) + m_volumeControl->SetMute(BOOL(m_muted)); + emit mutedChanged(m_muted); +} + +int MFPlayerSession::bufferStatus() +{ + if (!m_netsourceStatistics) + return 0; + PROPVARIANT var; + PROPERTYKEY key; + key.fmtid = MFNETSOURCE_STATISTICS; + key.pid = MFNETSOURCE_BUFFERPROGRESS_ID; + int progress = -1; + if (SUCCEEDED(m_netsourceStatistics->GetValue(key, &var))) { + progress = var.lVal; + } + PropVariantClear(&var); + +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "bufferStatus: progress = " << progress; +#endif + + return progress; +} + +QMediaTimeRange MFPlayerSession::availablePlaybackRanges() +{ + if (!m_netsourceStatistics) + return QMediaTimeRange(); + + qint64 start = 0, end = 0; + PROPVARIANT var; + PROPERTYKEY key; + key.fmtid = MFNETSOURCE_STATISTICS; + key.pid = MFNETSOURCE_SEEKRANGESTART_ID; + if (SUCCEEDED(m_netsourceStatistics->GetValue(key, &var))) { + start = qint64(var.uhVal.QuadPart / 10000); + key.pid = MFNETSOURCE_SEEKRANGEEND_ID; + if (SUCCEEDED(m_netsourceStatistics->GetValue(key, &var))) { + 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(this); + } else if (riid == IID_IUnknown) { + *ppvObject = static_cast(this); + } else { + *ppvObject = NULL; + return E_NOINTERFACE; + } + return S_OK; +} + +ULONG MFPlayerSession::AddRef(void) +{ + return 1; +} + +ULONG MFPlayerSession::Release(void) +{ + return 1; +} + +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); + return S_OK; + } else { + hr = m_session->BeginGetEvent(this, m_session); + if (FAILED(hr)) { + pEvent->Release(); + return S_OK; + } + } + + emit sessionEvent(pEvent); + return S_OK; +} + +void MFPlayerSession::handleSessionEvent(IMFMediaEvent *sessionEvent) +{ + HRESULT hrStatus = S_OK; + HRESULT hr = sessionEvent->GetStatus(&hrStatus); + if (FAILED(hr)) { + 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); + case MEError: + 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_scrubbing) + updatePendingCommands(CmdStart); + break; + case MESessionStopped: + if (m_status != QMediaPlayer::EndOfMedia) { + m_varStart.vt = VT_I8; + m_varStart.hVal.QuadPart = 0; + 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; + } + + 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 MEEndOfPresentation: + stop(); + changeStatus(QMediaPlayer::EndOfMedia); + m_varStart.vt = VT_I8; + //keep reporting the final position after end of media + m_varStart.hVal.QuadPart = m_duration; + break; + case MESessionEnded: + m_pendingState = NoPending; + m_state.command = CmdStop; + m_state.prevCmd = CmdNone; + m_request.command = CmdNone; + m_request.prevCmd = CmdNone; + break; + case MEEndOfPresentationSegment: + break; + case MEAudioSessionVolumeChanged: + if (m_volumeControl) { + float currentVolume = 1; + if (SUCCEEDED(m_volumeControl->GetMasterVolume(¤tVolume))) { + if (currentVolume != m_volume) { + m_volume = currentVolume; + emit volumeChanged(int(m_volume * 100)); + } + } + BOOL currentMuted = FALSE; + if (SUCCEEDED(m_volumeControl->GetMute(¤tMuted))) { + if (currentMuted != BOOL(m_muted)) { + m_muted = bool(currentMuted); + emit mutedChanged(m_muted); + } + } + } + break; + case MESessionTopologySet: { + if (SUCCEEDED(MFGetService(m_session, MR_POLICY_VOLUME_SERVICE, IID_PPV_ARGS(&m_volumeControl)))) { + m_volumeControl->SetMasterVolume(m_volume); + m_volumeControl->SetMute(m_muted); + } + DWORD dwCharacteristics = 0; + m_sourceResolver->mediaSource()->GetCharacteristics(&dwCharacteristics); + emit seekableUpdate(MFMEDIASOURCE_CAN_SEEK & dwCharacteristics); + changeStatus(QMediaPlayer::LoadedMedia); + } + 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)); + } + } + } + break; + } + + sessionEvent->Release(); +} + +void MFPlayerSession::updatePendingCommands(Command command) +{ + emit positionChanged(position()); + if (m_state.command != command || m_pendingState == NoPending) + return; + + // The current pending command has completed. + if (m_pendingState == SeekPending && m_state.prevCmd == CmdPause) { + m_pendingState = NoPending; + //if we have pending seek request, + //then we just keep current state to paused and continue the seek request, + //otherwise we will restore to pause state + if (m_request.command == CmdSeek) { + m_state.setCommand(CmdPause); + } else { + pause(); + return; + } + } + + 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); + } + 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; + } +} diff --git a/src/plugins/wmf/player/mfplayersession.h b/src/plugins/wmf/player/mfplayersession.h new file mode 100644 index 000000000..e6e46ada2 --- /dev/null +++ b/src/plugins/wmf/player/mfplayersession.h @@ -0,0 +1,212 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef MFPLAYERSESSION_H +#define MFPLAYERSESSION_H + +#include +#include + +#include "qmediaplayer.h" +#include "qmediaresource.h" +#include "qmediaservice.h" +#include "qmediatimerange.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE +class QMediaContent; +QT_END_NAMESPACE + +QT_USE_NAMESPACE + +class SourceResolver; +#ifndef Q_WS_SIMULATOR +class Evr9VideoWindowControl; +#endif +class MFAudioEndpointControl; +class MFVideoRendererControl; +class MFPlayerControl; +class MFMetaDataControl; + +class MFPlayerSession : public QObject, public IMFAsyncCallback +{ + Q_OBJECT + friend class SourceResolver; +public: + MFPlayerSession(QObject *parent = 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 QMediaContent &media, QIODevice *stream); + void stop(); + 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); + +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: + IMFMediaSession *m_session; + IMFPresentationClock *m_presentationClock; + IMFRateControl *m_rateControl; + IMFRateSupport *m_rateSupport; + IMFSimpleAudioVolume *m_volumeControl; + IPropertyStore *m_netsourceStatistics; + PROPVARIANT m_varStart; + UINT64 m_duration; + + enum Command + { + CmdNone = 0, + CmdStop, + CmdStart, + CmdPause, + CmdSeek, + CmdSeekResume, + }; + + 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; + + 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 createSession(); + void setupPlaybackTopology(IMFMediaSource *source, IMFPresentationDescriptor *sourcePD); + IMFTopologyNode* addSourceNode(IMFTopology* topology, IMFMediaSource* source, + IMFPresentationDescriptor* presentationDesc, IMFStreamDescriptor *streamDesc); + IMFTopologyNode* addOutputNode(IMFStreamDescriptor *streamDesc, MediaType& mediaType, IMFTopology* topology, DWORD sinkID); +}; + + +#endif diff --git a/src/plugins/wmf/player/mfstream.cpp b/src/plugins/wmf/player/mfstream.cpp new file mode 100644 index 000000000..fab854e4a --- /dev/null +++ b/src/plugins/wmf/player/mfstream.cpp @@ -0,0 +1,361 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "mfstream.h" +#include + +//MFStream is added for supporting QIODevice type of media source. +//It is used to delegate invocations from media foundation(through IMFByteStream) to QIODevice. + +MFStream::MFStream(QIODevice *stream, bool ownStream) + : m_cRef(1) + , m_stream(stream) + , m_ownStream(ownStream) + , m_currentReadResult(0) +{ + //Move to the thread of the stream object + //to make sure invocations on stream + //are happened in the same thread of stream object + this->moveToThread(stream->thread()); + connect(stream, SIGNAL(readyRead()), this, SLOT(handleReadyRead())); +} + +MFStream::~MFStream() +{ + if (m_currentReadResult) + m_currentReadResult->Release(); + if (m_ownStream) + m_stream->deleteLater(); +} + +//from IUnknown +STDMETHODIMP MFStream::QueryInterface(REFIID riid, LPVOID *ppvObject) +{ + if (!ppvObject) + return E_POINTER; + if (riid == IID_IMFByteStream) { + *ppvObject = static_cast(this); + } else if (riid == IID_IUnknown) { + *ppvObject = static_cast(this); + } else { + *ppvObject = NULL; + return E_NOINTERFACE; + } + AddRef(); + return S_OK; +} + +STDMETHODIMP_(ULONG) MFStream::AddRef(void) +{ + return InterlockedIncrement(&m_cRef); +} + +STDMETHODIMP_(ULONG) MFStream::Release(void) +{ + LONG cRef = InterlockedDecrement(&m_cRef); + if (cRef == 0) { + this->deleteLater(); + } + return cRef; +} + + +//from IMFByteStream +STDMETHODIMP MFStream::GetCapabilities(DWORD *pdwCapabilities) +{ + if (!pdwCapabilities) + return E_INVALIDARG; + *pdwCapabilities = MFBYTESTREAM_IS_READABLE; + if (!m_stream->isSequential()) + *pdwCapabilities |= MFBYTESTREAM_IS_SEEKABLE; + return S_OK; +} + +STDMETHODIMP MFStream::GetLength(QWORD *pqwLength) +{ + if (!pqwLength) + return E_INVALIDARG; + QMutexLocker locker(&m_mutex); + *pqwLength = QWORD(m_stream->size()); + return S_OK; +} + +STDMETHODIMP MFStream::SetLength(QWORD) +{ + return E_NOTIMPL; +} + +STDMETHODIMP MFStream::GetCurrentPosition(QWORD *pqwPosition) +{ + if (!pqwPosition) + return E_INVALIDARG; + QMutexLocker locker(&m_mutex); + *pqwPosition = m_stream->pos(); + return S_OK; +} + +STDMETHODIMP MFStream::SetCurrentPosition(QWORD qwPosition) +{ + QMutexLocker locker(&m_mutex); + //SetCurrentPosition may happend during the BeginRead/EndRead pair, + //refusing to execute SetCurrentPosition during that time seems to be + //the simplest workable solution + if (m_currentReadResult) + return S_FALSE; + + bool seekOK = m_stream->seek(qint64(qwPosition)); + if (seekOK) + return S_OK; + else + return S_FALSE; +} + +STDMETHODIMP MFStream::IsEndOfStream(BOOL *pfEndOfStream) +{ + if (!pfEndOfStream) + return E_INVALIDARG; + QMutexLocker locker(&m_mutex); + *pfEndOfStream = m_stream->atEnd() ? TRUE : FALSE; + return S_OK; +} + +STDMETHODIMP MFStream::Read(BYTE *pb, ULONG cb, ULONG *pcbRead) +{ + QMutexLocker locker(&m_mutex); + qint64 read = m_stream->read((char*)(pb), qint64(cb)); + if (pcbRead) + *pcbRead = ULONG(read); + return S_OK; +} + +STDMETHODIMP MFStream::BeginRead(BYTE *pb, ULONG cb, IMFAsyncCallback *pCallback, + IUnknown *punkState) +{ + if (!pCallback || !pb) + return E_INVALIDARG; + + Q_ASSERT(m_currentReadResult == NULL); + + AsyncReadState *state = new (std::nothrow) AsyncReadState(pb, cb); + if (state == NULL) + return E_OUTOFMEMORY; + + HRESULT hr = MFCreateAsyncResult(state, pCallback, punkState, &m_currentReadResult); + state->Release(); + if (FAILED(hr)) + return hr; + + QCoreApplication::postEvent(this, new QEvent(QEvent::User)); + return hr; +} + +STDMETHODIMP MFStream::EndRead(IMFAsyncResult* pResult, ULONG *pcbRead) +{ + if (!pcbRead) + return E_INVALIDARG; + IUnknown *pUnk; + pResult->GetObject(&pUnk); + AsyncReadState *state = static_cast(pUnk); + *pcbRead = state->bytesRead(); + pUnk->Release(); + + m_currentReadResult->Release(); + m_currentReadResult = NULL; + + return S_OK; +} + +STDMETHODIMP MFStream::Write(const BYTE *, ULONG, ULONG *) +{ + return E_NOTIMPL; +} + +STDMETHODIMP MFStream::BeginWrite(const BYTE *, ULONG , + IMFAsyncCallback *, + IUnknown *) +{ + return E_NOTIMPL; +} + +STDMETHODIMP MFStream::EndWrite(IMFAsyncResult *, + ULONG *) +{ + return E_NOTIMPL; +} + +STDMETHODIMP MFStream::Seek( + MFBYTESTREAM_SEEK_ORIGIN SeekOrigin, + LONGLONG llSeekOffset, + DWORD, + QWORD *pqwCurrentPosition) +{ + QMutexLocker locker(&m_mutex); + if (m_currentReadResult) + return S_FALSE; + + qint64 pos = qint64(llSeekOffset); + switch (SeekOrigin) { + case msoCurrent: + pos += m_stream->pos(); + break; + } + bool seekOK = m_stream->seek(pos); + if (*pqwCurrentPosition) + *pqwCurrentPosition = pos; + if (seekOK) + return S_OK; + else + return S_FALSE; +} + +STDMETHODIMP MFStream::Flush() +{ + return E_NOTIMPL; +} + +STDMETHODIMP MFStream::Close() +{ + QMutexLocker locker(&m_mutex); + if (m_ownStream) + m_stream->close(); + return S_OK; +} + +void MFStream::doRead() +{ + bool readDone = true; + IUnknown *pUnk = NULL; + HRESULT hr = m_currentReadResult->GetObject(&pUnk); + if (SUCCEEDED(hr)) { + //do actual read + AsyncReadState *state = static_cast(pUnk); + ULONG cbRead; + Read(state->pb(), state->cb() - state->bytesRead(), &cbRead); + pUnk->Release(); + + state->setBytesRead(cbRead + state->bytesRead()); + if (state->cb() > state->bytesRead() && !m_stream->atEnd()) { + readDone = false; + } + } + + if (readDone) { + //now inform the original caller + m_currentReadResult->SetStatus(hr); + MFInvokeCallback(m_currentReadResult); + } +} + + +void MFStream::handleReadyRead() +{ + doRead(); +} + +void MFStream::customEvent(QEvent *event) +{ + if (event->type() != QEvent::User) { + QObject::customEvent(event); + return; + } + doRead(); +} + +//AsyncReadState is a helper class used in BeginRead for asynchronous operation +//to record some BeginRead parameters, so these parameters could be +//used later when actually executing the read operation in another thread. +MFStream::AsyncReadState::AsyncReadState(BYTE *pb, ULONG cb) + : m_cRef(1) + , m_pb(pb) + , m_cb(cb) + , m_cbRead(0) +{ +} + +//from IUnknown +STDMETHODIMP MFStream::AsyncReadState::QueryInterface(REFIID riid, LPVOID *ppvObject) +{ + if (!ppvObject) + return E_POINTER; + + if (riid == IID_IUnknown) { + *ppvObject = static_cast(this); + } else { + *ppvObject = NULL; + return E_NOINTERFACE; + } + AddRef(); + return S_OK; +} + +STDMETHODIMP_(ULONG) MFStream::AsyncReadState::AddRef(void) +{ + return InterlockedIncrement(&m_cRef); +} + +STDMETHODIMP_(ULONG) MFStream::AsyncReadState::Release(void) +{ + LONG cRef = InterlockedDecrement(&m_cRef); + if (cRef == 0) + delete this; + // For thread safety, return a temporary variable. + return cRef; +} + +BYTE* MFStream::AsyncReadState::pb() const +{ + return m_pb; +} + +ULONG MFStream::AsyncReadState::cb() const +{ + return m_cb; +} + +ULONG MFStream::AsyncReadState::bytesRead() const +{ + return m_cbRead; +} + +void MFStream::AsyncReadState::setBytesRead(ULONG cbRead) +{ + m_cbRead = cbRead; +} diff --git a/src/plugins/wmf/player/mfstream.h b/src/plugins/wmf/player/mfstream.h new file mode 100644 index 000000000..985d42e96 --- /dev/null +++ b/src/plugins/wmf/player/mfstream.h @@ -0,0 +1,149 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef MFSTREAM_H +#define MFSTREAM_H + +#include +#include +#include +#include +#include + +QT_USE_NAMESPACE + +class MFStream : public QObject, public IMFByteStream +{ + Q_OBJECT +public: + MFStream(QIODevice *stream, bool ownStream); + + ~MFStream(); + + //from IUnknown + STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject); + + STDMETHODIMP_(ULONG) AddRef(void); + + STDMETHODIMP_(ULONG) Release(void); + + + //from IMFByteStream + STDMETHODIMP GetCapabilities(DWORD *pdwCapabilities); + + STDMETHODIMP GetLength(QWORD *pqwLength); + + STDMETHODIMP SetLength(QWORD); + + STDMETHODIMP GetCurrentPosition(QWORD *pqwPosition); + + STDMETHODIMP SetCurrentPosition(QWORD qwPosition); + + STDMETHODIMP IsEndOfStream(BOOL *pfEndOfStream); + + STDMETHODIMP Read(BYTE *pb, ULONG cb, ULONG *pcbRead); + + STDMETHODIMP BeginRead(BYTE *pb, ULONG cb, IMFAsyncCallback *pCallback, + IUnknown *punkState); + + STDMETHODIMP EndRead(IMFAsyncResult* pResult, ULONG *pcbRead); + + STDMETHODIMP Write(const BYTE *, ULONG, ULONG *); + + STDMETHODIMP BeginWrite(const BYTE *, ULONG , + IMFAsyncCallback *, + IUnknown *); + + STDMETHODIMP EndWrite(IMFAsyncResult *, + ULONG *); + + STDMETHODIMP Seek( + MFBYTESTREAM_SEEK_ORIGIN SeekOrigin, + LONGLONG llSeekOffset, + DWORD, + QWORD *pqwCurrentPosition); + + STDMETHODIMP Flush(); + + STDMETHODIMP Close(); + +private: + class AsyncReadState : public IUnknown + { + public: + AsyncReadState(BYTE *pb, ULONG cb); + + //from IUnknown + STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject); + + STDMETHODIMP_(ULONG) AddRef(void); + + STDMETHODIMP_(ULONG) Release(void); + + BYTE* pb() const; + ULONG cb() const; + ULONG bytesRead() const; + + void setBytesRead(ULONG cbRead); + + private: + long m_cRef; + BYTE *m_pb; + ULONG m_cb; + ULONG m_cbRead; + }; + + long m_cRef; + QIODevice *m_stream; + bool m_ownStream; + DWORD m_workQueueId; + QMutex m_mutex; + + void doRead(); + +private Q_SLOTS: + void handleReadyRead(); + +protected: + void customEvent(QEvent *event); + IMFAsyncResult *m_currentReadResult; +}; + +#endif \ No newline at end of file diff --git a/src/plugins/wmf/player/mfvideorenderercontrol.cpp b/src/plugins/wmf/player/mfvideorenderercontrol.cpp new file mode 100644 index 000000000..ef9ac7821 --- /dev/null +++ b/src/plugins/wmf/player/mfvideorenderercontrol.cpp @@ -0,0 +1,2201 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "mfvideorenderercontrol.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "guiddef.h" +#include + +//#define DEBUG_MEDIAFOUNDATION +#define PAD_TO_DWORD(x) (((x) + 3) & ~3) + +namespace +{ + class MediaSampleVideoBuffer : public QAbstractVideoBuffer + { + public: + MediaSampleVideoBuffer(IMFMediaBuffer *buffer, int bytesPerLine) + : QAbstractVideoBuffer(NoHandle) + , m_buffer(buffer) + , m_bytesPerLine(bytesPerLine) + , m_mapMode(NotMapped) + { + buffer->AddRef(); + } + + ~MediaSampleVideoBuffer() + { + m_buffer->Release(); + } + + uchar *map(MapMode mode, int *numBytes, int *bytesPerLine) + { + if (m_mapMode == NotMapped && mode != NotMapped) { + BYTE *bytes; + DWORD length; + HRESULT hr = m_buffer->Lock(&bytes, NULL, &length); + if (SUCCEEDED(hr)) { + if (numBytes) + *numBytes = int(length); + + if (bytesPerLine) + *bytesPerLine = m_bytesPerLine; + + m_mapMode = mode; + return reinterpret_cast(bytes); + } else { + qWarning("Faild to lock mf buffer!"); + } + } + return 0; + } + + void unmap() + { + if (m_mapMode == NotMapped) + return; + m_mapMode = NotMapped; + m_buffer->Unlock(); + } + + MapMode mapMode() const + { + return m_mapMode; + } + + private: + IMFMediaBuffer *m_buffer; + int m_bytesPerLine; + MapMode m_mapMode; + }; + + template + class AsyncCallback : public IMFAsyncCallback + { + public: + typedef HRESULT (T::*InvokeFn)(IMFAsyncResult *pAsyncResult); + + AsyncCallback(T *pParent, InvokeFn fn) : m_pParent(pParent), m_pInvokeFn(fn) + { + } + + // IUnknown + STDMETHODIMP QueryInterface(REFIID iid, void** ppv) + { + if (!ppv) + { + return E_POINTER; + } + if (iid == __uuidof(IUnknown)) + { + *ppv = static_cast(static_cast(this)); + } + else if (iid == __uuidof(IMFAsyncCallback)) + { + *ppv = static_cast(this); + } + else + { + *ppv = NULL; + return E_NOINTERFACE; + } + AddRef(); + return S_OK; + } + STDMETHODIMP_(ULONG) AddRef() + { + // Delegate to parent class. + return m_pParent->AddRef(); + } + STDMETHODIMP_(ULONG) Release() + { + // Delegate to parent class. + return m_pParent->Release(); + } + + // IMFAsyncCallback methods + STDMETHODIMP GetParameters(DWORD*, DWORD*) + { + // Implementation of this method is optional. + return E_NOTIMPL; + } + + STDMETHODIMP Invoke(IMFAsyncResult* pAsyncResult) + { + return (m_pParent->*m_pInvokeFn)(pAsyncResult); + } + + T *m_pParent; + InvokeFn m_pInvokeFn; + }; + + + // Custom interface for handling IMFStreamSink::PlaceMarker calls asynchronously. + MIDL_INTERFACE("a3ff32de-1031-438a-8b47-82f8acda59b7") + IMarker : public IUnknown + { + virtual STDMETHODIMP GetMarkerType(MFSTREAMSINK_MARKER_TYPE *pType) = 0; + virtual STDMETHODIMP GetMarkerValue(PROPVARIANT *pvar) = 0; + virtual STDMETHODIMP GetContext(PROPVARIANT *pvar) = 0; + }; + + class Marker : public IMarker + { + public: + static HRESULT Create( + MFSTREAMSINK_MARKER_TYPE eMarkerType, + const PROPVARIANT* pvarMarkerValue, // Can be NULL. + const PROPVARIANT* pvarContextValue, // Can be NULL. + IMarker **ppMarker) + { + if (ppMarker == NULL) + return E_POINTER; + + HRESULT hr = S_OK; + Marker *pMarker = new Marker(eMarkerType); + if (pMarker == NULL) + hr = E_OUTOFMEMORY; + + // Copy the marker data. + if (SUCCEEDED(hr) && pvarMarkerValue) + hr = PropVariantCopy(&pMarker->m_varMarkerValue, pvarMarkerValue); + + if (SUCCEEDED(hr) && pvarContextValue) + hr = PropVariantCopy(&pMarker->m_varContextValue, pvarContextValue); + + if (SUCCEEDED(hr)) { + *ppMarker = pMarker; + (*ppMarker)->AddRef(); + } + + if (pMarker) + pMarker->Release(); + + return hr; + } + + // IUnknown methods. + STDMETHODIMP QueryInterface(REFIID iid, void** ppv) + { + if (!ppv) + return E_POINTER; + if (iid == IID_IUnknown) { + *ppv = static_cast(this); + } else if (iid == __uuidof(IMarker)) { + *ppv = static_cast(this); + } else { + *ppv = NULL; + return E_NOINTERFACE; + } + AddRef(); + return S_OK; + } + + STDMETHODIMP_(ULONG) AddRef() + { + return InterlockedIncrement(&m_cRef); + } + + STDMETHODIMP_(ULONG) Release() + { + LONG cRef = InterlockedDecrement(&m_cRef); + if (cRef == 0) + delete this; + // For thread safety, return a temporary variable. + return cRef; + } + + STDMETHODIMP GetMarkerType(MFSTREAMSINK_MARKER_TYPE *pType) + { + if (pType == NULL) + return E_POINTER; + *pType = m_eMarkerType; + return S_OK; + } + + STDMETHODIMP GetMarkerValue(PROPVARIANT *pvar) + { + if (pvar == NULL) + return E_POINTER; + return PropVariantCopy(pvar, &m_varMarkerValue); + } + + STDMETHODIMP GetContext(PROPVARIANT *pvar) + { + if (pvar == NULL) + return E_POINTER; + return PropVariantCopy(pvar, &m_varContextValue); + } + + protected: + MFSTREAMSINK_MARKER_TYPE m_eMarkerType; + PROPVARIANT m_varMarkerValue; + PROPVARIANT m_varContextValue; + + private: + long m_cRef; + + Marker(MFSTREAMSINK_MARKER_TYPE eMarkerType) : m_cRef(1), m_eMarkerType(eMarkerType) + { + PropVariantInit(&m_varMarkerValue); + PropVariantInit(&m_varContextValue); + } + + virtual ~Marker() + { + PropVariantClear(&m_varMarkerValue); + PropVariantClear(&m_varContextValue); + } + }; + + class MediaStream : public QObject, public IMFStreamSink, public IMFMediaTypeHandler + { + Q_OBJECT + friend class MFVideoRendererControl; + public: + static const DWORD DEFAULT_MEDIA_STREAM_ID = 0x0; + + MediaStream(IMFMediaSink *parent, MFVideoRendererControl *rendererControl) + : m_cRef(1) + , m_eventQueue(0) + , m_shutdown(false) + , m_surface(0) + , m_state(State_TypeNotSet) + , m_currentFormatIndex(-1) + , m_bytesPerLine(0) + , m_workQueueId(0) + , m_workQueueCB(this, &MediaStream::onDispatchWorkItem) + , m_finalizeResult(0) + , m_scheduledBuffer(0) + , m_presentationClock(0) + , m_currentMediaType(0) + , m_prerolling(false) + , m_prerollTargetTime(0) + , m_startTime(0) + , m_rendererControl(rendererControl) + { + m_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(this); + } else if (riid == IID_IMFMediaEventGenerator) { + *ppvObject = static_cast(this); + } else if (riid == IID_IMFMediaTypeHandler) { + *ppvObject = static_cast(this); + } else if (riid == IID_IUnknown) { + *ppvObject = static_cast(static_cast(this)); + } else { + *ppvObject = NULL; + return E_NOINTERFACE; + } + AddRef(); + return S_OK; + } + + STDMETHODIMP_(ULONG) AddRef(void) + { + return InterlockedIncrement(&m_cRef); + } + + STDMETHODIMP_(ULONG) Release(void) + { + LONG cRef = InterlockedDecrement(&m_cRef); + if (cRef == 0) + delete this; + // For thread safety, return a temporary variable. + return cRef; + } + + //from IMFMediaEventGenerator + STDMETHODIMP GetEvent( + DWORD dwFlags, + IMFMediaEvent **ppEvent) + { + // GetEvent can block indefinitely, so we don't hold the lock. + // This requires some juggling with the event queue pointer. + HRESULT hr = S_OK; + IMFMediaEventQueue *queue = NULL; + + m_mutex.lock(); + if (m_shutdown) + hr = MF_E_SHUTDOWN; + if (SUCCEEDED(hr)) { + queue = m_eventQueue; + queue->AddRef(); + } + m_mutex.unlock(); + + // Now get the event. + if (SUCCEEDED(hr)) { + hr = queue->GetEvent(dwFlags, ppEvent); + queue->Release(); + } + + return hr; + } + + STDMETHODIMP BeginGetEvent( + IMFAsyncCallback *pCallback, + IUnknown *punkState) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + return m_eventQueue->BeginGetEvent(pCallback, punkState); + } + + STDMETHODIMP EndGetEvent( + IMFAsyncResult *pResult, + IMFMediaEvent **ppEvent) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + return m_eventQueue->EndGetEvent(pResult, ppEvent); + } + + STDMETHODIMP QueueEvent( + MediaEventType met, + REFGUID guidExtendedType, + HRESULT hrStatus, + const PROPVARIANT *pvValue) + { +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "MediaStream::QueueEvent" << met; +#endif + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + return m_eventQueue->QueueEventParamVar(met, guidExtendedType, hrStatus, pvValue); + } + + //from IMFStreamSink + STDMETHODIMP GetMediaSink( + IMFMediaSink **ppMediaSink) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + else if (!ppMediaSink) + return E_INVALIDARG; + + m_sink->AddRef(); + *ppMediaSink = m_sink; + return S_OK; + } + + STDMETHODIMP GetIdentifier( + DWORD *pdwIdentifier) + { + *pdwIdentifier = MediaStream::DEFAULT_MEDIA_STREAM_ID; + return S_OK; + } + + STDMETHODIMP GetMediaTypeHandler( + IMFMediaTypeHandler **ppHandler) + { + LPVOID handler = NULL; + HRESULT hr = QueryInterface(IID_IMFMediaTypeHandler, &handler); + *ppHandler = (IMFMediaTypeHandler*)(handler); + return hr; + } + + STDMETHODIMP ProcessSample( + IMFSample *pSample) + { + if (pSample == NULL) + return E_INVALIDARG; + HRESULT hr = S_OK; + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + + if (!m_prerolling) { + hr = validateOperation(OpProcessSample); + if (FAILED(hr)) + return hr; + } + + pSample->AddRef(); + m_sampleQueue.push_back(pSample); + + // Unless we are paused, start an async operation to dispatch the next sample. + if (m_state != State_Paused) + hr = queueAsyncOperation(OpProcessSample); + + return hr; + } + + STDMETHODIMP PlaceMarker( + MFSTREAMSINK_MARKER_TYPE eMarkerType, + const PROPVARIANT *pvarMarkerValue, + const PROPVARIANT *pvarContextValue) + { + HRESULT hr = S_OK; + QMutexLocker locker(&m_mutex); + IMarker *pMarker = NULL; + if (m_shutdown) + return MF_E_SHUTDOWN; + + hr = validateOperation(OpPlaceMarker); + if (FAILED(hr)) + return hr; + + // Create a marker object and put it on the sample queue. + hr = Marker::Create(eMarkerType, pvarMarkerValue, pvarContextValue, &pMarker); + if (FAILED(hr)) + return hr; + + m_sampleQueue.push_back(pMarker); + + // Unless we are paused, start an async operation to dispatch the next sample/marker. + if (m_state != State_Paused) + hr = queueAsyncOperation(OpPlaceMarker); // Increments ref count on pOp. + return hr; + } + + STDMETHODIMP Flush( void) + { +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "MediaStream::Flush"; +#endif + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + // Note: Even though we are flushing data, we still need to send + // any marker events that were queued. + clearBufferCache(); + return processSamplesFromQueue(DropSamples); + } + + //from IMFMediaTypeHandler + STDMETHODIMP IsMediaTypeSupported( + IMFMediaType *pMediaType, + IMFMediaType **ppMediaType) + { + if (ppMediaType) + *ppMediaType = NULL; + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + + int index = getMediaTypeIndex(pMediaType); + if (index < 0) { + if (ppMediaType && m_mediaTypes.size() > 0) { + *ppMediaType = m_mediaTypes[0]; + (*ppMediaType)->AddRef(); + } + return MF_E_INVALIDMEDIATYPE; + } + + BOOL compressed = TRUE; + pMediaType->IsCompressedFormat(&compressed); + if (compressed) { + if (ppMediaType && (SUCCEEDED(MFCreateMediaType(ppMediaType)))) { + (*ppMediaType)->CopyAllItems(pMediaType); + (*ppMediaType)->SetUINT32(MF_MT_FIXED_SIZE_SAMPLES, TRUE); + (*ppMediaType)->SetUINT32(MF_MT_COMPRESSED, FALSE); + (*ppMediaType)->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE); + } + return MF_E_INVALIDMEDIATYPE; + } + + return S_OK; + } + + STDMETHODIMP GetMediaTypeCount( + DWORD *pdwTypeCount) + { + if (pdwTypeCount == NULL) + return E_INVALIDARG; + QMutexLocker locker(&m_mutex); + *pdwTypeCount = DWORD(m_mediaTypes.size()); + return S_OK; + } + + STDMETHODIMP GetMediaTypeByIndex( + DWORD dwIndex, + IMFMediaType **ppType) + { + if (ppType == NULL) + return E_INVALIDARG; + HRESULT hr = S_OK; + QMutexLocker locker(&m_mutex); + if (m_shutdown) + hr = MF_E_SHUTDOWN; + + if (SUCCEEDED(hr)) { + if (dwIndex >= DWORD(m_mediaTypes.size())) + hr = MF_E_NO_MORE_TYPES; + } + + if (SUCCEEDED(hr)) { + *ppType = m_mediaTypes[dwIndex]; + (*ppType)->AddRef(); + } + return hr; + } + + STDMETHODIMP SetCurrentMediaType( + IMFMediaType *pMediaType) + { + HRESULT hr = S_OK; + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + + DWORD flag = MF_MEDIATYPE_EQUAL_MAJOR_TYPES | + MF_MEDIATYPE_EQUAL_FORMAT_TYPES | + MF_MEDIATYPE_EQUAL_FORMAT_DATA; + + if (m_currentMediaType && (m_currentMediaType->IsEqual(pMediaType, &flag) == S_OK)) + return S_OK; + + hr = validateOperation(OpSetMediaType); + + if (SUCCEEDED(hr)) { + int index = getMediaTypeIndex(pMediaType); + if (index >= 0) { + UINT64 size; + hr = pMediaType->GetUINT64(MF_MT_FRAME_SIZE, &size); + if (SUCCEEDED(hr)) { + m_currentFormatIndex = index; + int width = int(HI32(size)); + int height = int(LO32(size)); + QVideoSurfaceFormat format(QSize(width, height), m_pixelFormats[index]); + m_surfaceFormat = format; + + if (FAILED(pMediaType->GetUINT32(MF_MT_DEFAULT_STRIDE, (UINT32*)&m_bytesPerLine))) { + m_bytesPerLine = getBytesPerLine(format); + } + + m_state = State_Ready; + if (m_currentMediaType) + m_currentMediaType->Release(); + m_currentMediaType = pMediaType; + pMediaType->AddRef(); + } + } else { + hr = MF_E_INVALIDREQUEST; + } + } + return hr; + } + + STDMETHODIMP GetCurrentMediaType( + IMFMediaType **ppMediaType) + { + if (ppMediaType == NULL) + return E_INVALIDARG; + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + if (m_currentFormatIndex < 0) + return MF_E_NOT_INITIALIZED; + *ppMediaType = m_currentMediaType; + (*ppMediaType)->AddRef(); + return S_OK; + } + + STDMETHODIMP GetMajorType( + GUID *pguidMajorType) + { + if (pguidMajorType == NULL) + return E_INVALIDARG; + *pguidMajorType = MFMediaType_Video; + return S_OK; + } + + // + void setSurface(QAbstractVideoSurface *surface) + { + m_mutex.lock(); + m_surface = surface; + m_mutex.unlock(); + supportedFormatsChanged(); + } + + void setClock(IMFPresentationClock *presentationClock) + { + QMutexLocker locker(&m_mutex); + if (!m_shutdown) { + if (m_presentationClock) + m_presentationClock->Release(); + m_presentationClock = presentationClock; + if (m_presentationClock) + m_presentationClock->AddRef(); + } + } + + void shutdown() + { + QMutexLocker locker(&m_mutex); + Q_ASSERT(!m_shutdown); + + if (m_currentMediaType) { + m_currentMediaType->Release(); + m_currentMediaType = NULL; + m_currentFormatIndex = -1; + } + + if (m_eventQueue) + m_eventQueue->Shutdown(); + + MFUnlockWorkQueue(m_workQueueId); + + if (m_presentationClock) { + m_presentationClock->Release(); + m_presentationClock = NULL; + } + + if (m_scheduledBuffer) { + m_scheduledBuffer->Release(); + m_scheduledBuffer = NULL; + } + + clearMediaTypes(); + clearSampleQueue(); + clearBufferCache(); + + if (m_eventQueue) { + m_eventQueue->Release(); + m_eventQueue = NULL; + } + + m_shutdown = true; + } + + HRESULT startPreroll(MFTIME hnsUpcomingStartTime) + { + QMutexLocker locker(&m_mutex); + HRESULT hr = validateOperation(OpPreroll); + if (SUCCEEDED(hr)) { + m_prerollTargetTime = hnsUpcomingStartTime; + hr = queueAsyncOperation(OpPreroll); + } + return hr; + } + + HRESULT finalize(IMFAsyncCallback *pCallback, IUnknown *punkState) + { + QMutexLocker locker(&m_mutex); + HRESULT hr = S_OK; + hr = validateOperation(OpFinalize); + if (SUCCEEDED(hr) && m_finalizeResult != NULL) + hr = MF_E_INVALIDREQUEST; // The operation is already pending. + + // Create and store the async result object. + if (SUCCEEDED(hr)) + hr = MFCreateAsyncResult(NULL, pCallback, punkState, &m_finalizeResult); + + if (SUCCEEDED(hr)) { + m_state = State_Finalized; + hr = queueAsyncOperation(OpFinalize); + } + return hr; + } + + HRESULT start(MFTIME start) + { +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "MediaStream::start" << start; +#endif + HRESULT hr = S_OK; + QMutexLocker locker(&m_mutex); + if (m_rate != 0) + hr = validateOperation(OpStart); + + if (SUCCEEDED(hr)) { + MFTIME sysTime; + if (start != PRESENTATION_CURRENT_POSITION) + m_startTime = start; // Cache the start time. + else + m_presentationClock->GetCorrelatedTime(0, &m_startTime, &sysTime); + m_state = State_Started; + hr = queueAsyncOperation(OpStart); + } + return hr; + } + + HRESULT restart() + { +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "MediaStream::restart"; +#endif + QMutexLocker locker(&m_mutex); + HRESULT hr = validateOperation(OpRestart); + if (SUCCEEDED(hr)) { + m_state = State_Started; + hr = queueAsyncOperation(OpRestart); + } + return hr; + } + + HRESULT stop() + { +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "MediaStream::stop"; +#endif + QMutexLocker locker(&m_mutex); + HRESULT hr = validateOperation(OpStop); + if (SUCCEEDED(hr)) { + m_state = State_Stopped; + hr = queueAsyncOperation(OpStop); + } + return hr; + } + + HRESULT pause() + { +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "MediaStream::pause"; +#endif + QMutexLocker locker(&m_mutex); + HRESULT hr = validateOperation(OpPause); + if (SUCCEEDED(hr)) { + m_state = State_Paused; + hr = queueAsyncOperation(OpPause); + } + return hr; + } + + HRESULT setRate(float rate) + { +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "MediaStream::setRate" << rate; +#endif + QMutexLocker locker(&m_mutex); + m_rate = rate; + queueEvent(MEStreamSinkRateChanged, GUID_NULL, S_OK, NULL); + return S_OK; + } + + void supportedFormatsChanged() + { + QMutexLocker locker(&m_mutex); + m_pixelFormats.clear(); + clearMediaTypes(); + QList formats = m_surface->supportedPixelFormats(); + foreach (QVideoFrame::PixelFormat format, formats) { + IMFMediaType *mediaType; + if (FAILED(MFCreateMediaType(&mediaType))) { + qWarning("Failed to create mf media type!"); + continue; + } + mediaType->SetUINT32(MF_MT_FIXED_SIZE_SAMPLES, TRUE); + mediaType->SetUINT32(MF_MT_COMPRESSED, FALSE); + mediaType->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE); + mediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); + switch (format) { + case QVideoFrame::Format_ARGB32: + case QVideoFrame::Format_ARGB32_Premultiplied: + mediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_ARGB32); + break; + case QVideoFrame::Format_RGB32: + mediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32); + break; + case QVideoFrame::Format_RGB24: + mediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB24); + break; + case QVideoFrame::Format_RGB565: + mediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB565); + break; + case QVideoFrame::Format_RGB555: + mediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB555); + break; + case QVideoFrame::Format_AYUV444: + case QVideoFrame::Format_AYUV444_Premultiplied: + mediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_AYUV); + break; + case QVideoFrame::Format_YUV420P: + mediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_I420); + break; + case QVideoFrame::Format_UYVY: + mediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_UYVY); + break; + case QVideoFrame::Format_YV12: + mediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_YV12); + break; + case QVideoFrame::Format_NV12: + mediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_NV12); + break; + default: + mediaType->Release(); + continue; + } + m_pixelFormats.push_back(format); + m_mediaTypes.push_back(mediaType); + } + } + + void present() + { + QMutexLocker locker(&m_mutex); + if (!m_scheduledBuffer) + return; + m_surface->present(QVideoFrame( + new MediaSampleVideoBuffer(m_scheduledBuffer, m_bytesPerLine), + m_surfaceFormat.frameSize(), + m_surfaceFormat.pixelFormat())); + m_scheduledBuffer->Release(); + m_scheduledBuffer = NULL; + if (m_rate != 0) + schedulePresentation(true); + } + + enum + { + StartSurface = QEvent::User, + StopSurface, + FlushSurface, + PresentSurface + }; + + class PresentEvent : public QEvent + { + public: + PresentEvent(MFTIME targetTime) + : QEvent(QEvent::Type(PresentSurface)) + , m_time(targetTime) + { + } + + int targetTime() + { + return m_time; + } + + private: + MFTIME m_time; + }; + + protected: + void customEvent(QEvent *event) + { + QMutexLocker locker(&m_mutex); + if (event->type() == StartSurface) { + if (m_state == State_WaitForSurfaceStart) { + m_startResult = startSurface(); + queueAsyncOperation(OpStart); + } + } else if (event->type() == StopSurface) { + stopSurface(); + } else { + QObject::customEvent(event); + } + } + HRESULT m_startResult; + + private: + HRESULT startSurface() + { + if (!m_surface->isFormatSupported(m_surfaceFormat)) + return S_FALSE; + if (!m_surface->start(m_surfaceFormat)) + return S_FALSE; + return S_OK; + } + + void stopSurface() + { + m_surface->stop(); + } + + enum FlushState + { + DropSamples = 0, + WriteSamples + }; + + // State enum: Defines the current state of the stream. + enum State + { + State_TypeNotSet = 0, // No media type is set + State_Ready, // Media type is set, Start has never been called. + State_Started, + State_Paused, + State_Stopped, + State_WaitForSurfaceStart, + State_Finalized, + State_Count = State_Finalized + 1 // Number of states + }; + + // StreamOperation: Defines various operations that can be performed on the stream. + enum StreamOperation + { + OpSetMediaType = 0, + OpPreroll, + OpStart, + 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(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 m_workQueueCB; + QList m_sampleQueue; + IMFAsyncResult *m_finalizeResult; // Result object for Finalize operation. + MFTIME m_startTime; // Presentation time when the clock started. + + bool m_shutdown; + QList m_mediaTypes; + QList m_pixelFormats; + int m_currentFormatIndex; + int m_bytesPerLine; + QVideoSurfaceFormat m_surfaceFormat; + QAbstractVideoSurface* m_surface; + MFVideoRendererControl *m_rendererControl; + + void clearMediaTypes() + { + foreach (IMFMediaType* mediaType, m_mediaTypes) + mediaType->Release(); + m_mediaTypes.clear(); + } + + int getMediaTypeIndex(IMFMediaType *mt) + { + GUID majorType; + if (FAILED(mt->GetMajorType(&majorType))) + return -1; + if (majorType != MFMediaType_Video) + return -1; + + GUID subType; + if (FAILED(mt->GetGUID(MF_MT_SUBTYPE, &subType))) + return -1; + + for (int index = 0; index < m_mediaTypes.size(); ++index) { + GUID st; + m_mediaTypes[index]->GetGUID(MF_MT_SUBTYPE, &st); + if (st == subType) + return index; + } + return -1; + } + + int getBytesPerLine(const QVideoSurfaceFormat &format) + { + switch (format.pixelFormat()) { + // 32 bpp packed formats. + case QVideoFrame::Format_RGB32: + case QVideoFrame::Format_AYUV444: + return format.frameWidth() * 4; + // 24 bpp packed formats. + case QVideoFrame::Format_RGB24: + return PAD_TO_DWORD(format.frameWidth() * 3); + // 16 bpp packed formats. + case QVideoFrame::Format_RGB565: + case QVideoFrame::Format_RGB555: + case QVideoFrame::Format_YUYV: + case QVideoFrame::Format_UYVY: + return PAD_TO_DWORD(format.frameWidth() * 2); + // Planar formats. + case QVideoFrame::Format_IMC1: + case QVideoFrame::Format_IMC2: + case QVideoFrame::Format_IMC3: + case QVideoFrame::Format_IMC4: + case QVideoFrame::Format_YV12: + case QVideoFrame::Format_NV12: + case QVideoFrame::Format_YUV420P: + return PAD_TO_DWORD(format.frameWidth()); + default: + return 0; + } + } + + // Callback for MFPutWorkItem. + HRESULT onDispatchWorkItem(IMFAsyncResult* pAsyncResult) + { + QMutexLocker locker(&m_mutex); + HRESULT hr = S_OK; + IUnknown *pState = NULL; + hr = pAsyncResult->GetState(&pState); + if (SUCCEEDED(hr)) { + // The state object is an AsncOperation object. + AsyncOperation *pOp = (AsyncOperation*)pState; + StreamOperation op = pOp->m_op; + switch (op) { + case OpStart: + endPreroll(S_FALSE); + if (m_state == State_WaitForSurfaceStart) { + hr = m_startResult; + m_state = State_Started; + } else if (!m_surface->isActive()) { + if (thread() == QThread::currentThread()) { + hr = startSurface(); + } + else { + m_state = State_WaitForSurfaceStart; + QCoreApplication::postEvent(m_rendererControl, new QChildEvent(QEvent::Type(StartSurface), this)); + break; + } + } + case OpRestart: + endPreroll(S_FALSE); + if (SUCCEEDED(hr)) { + // Send MEStreamSinkStarted. + hr = queueEvent(MEStreamSinkStarted, GUID_NULL, hr, NULL); + // Kick things off by requesting samples... + schedulePresentation(true); + // There might be samples queue from earlier (ie, while paused). + if (SUCCEEDED(hr)) + hr = processSamplesFromQueue(WriteSamples); + } + break; + case OpPreroll: + beginPreroll(); + break; + case OpStop: + // Drop samples from queue. + hr = processSamplesFromQueue(DropSamples); + if (m_scheduledBuffer) { + m_scheduledBuffer->Release(); + m_scheduledBuffer = NULL; + } + // Send the event even if the previous call failed. + hr = queueEvent(MEStreamSinkStopped, GUID_NULL, hr, NULL); + if (m_surface->isActive()) { + if (thread() == QThread::currentThread()) { + stopSurface(); + } + else { + QCoreApplication::postEvent(m_rendererControl, new QChildEvent(QEvent::Type(StopSurface), this)); + } + } + break; + case OpPause: + hr = queueEvent(MEStreamSinkPaused, GUID_NULL, hr, NULL); + break; + case OpSetRate: + //TODO: + break; + case OpProcessSample: + case OpPlaceMarker: + hr = dispatchProcessSample(pOp); + break; + case OpFinalize: + endPreroll(S_FALSE); + hr = dispatchFinalize(pOp); + break; + } + } + + if (pState) + pState->Release(); + return hr; + } + + + HRESULT queueEvent(MediaEventType met, REFGUID guidExtendedType, HRESULT hrStatus, const PROPVARIANT* pvValue) + { + HRESULT hr = S_OK; + if (m_shutdown) + hr = MF_E_SHUTDOWN; + if (SUCCEEDED(hr)) + hr = m_eventQueue->QueueEventParamVar(met, guidExtendedType, hrStatus, pvValue); + return hr; + } + + HRESULT validateOperation(StreamOperation op) + { + Q_ASSERT(!m_shutdown); + if (ValidStateMatrix[m_state][op]) + return S_OK; + else + return MF_E_INVALIDREQUEST; + } + + HRESULT queueAsyncOperation(StreamOperation op) + { + HRESULT hr = S_OK; + AsyncOperation *asyncOp = new AsyncOperation(op); + if (asyncOp == NULL) + hr = E_OUTOFMEMORY; + + if (SUCCEEDED(hr)) + hr = MFPutWorkItem(m_workQueueId, &m_workQueueCB, asyncOp); + + if (asyncOp) + asyncOp->Release(); + + return hr; + } + + HRESULT processSamplesFromQueue(FlushState bFlushData) + { + HRESULT hr = S_OK; + QList::Iterator pos = m_sampleQueue.begin(); + // Enumerate all of the samples/markers in the queue. + while (pos != m_sampleQueue.end()) { + IUnknown *pUnk = NULL; + IMarker *pMarker = NULL; + IMFSample *pSample = NULL; + pUnk = *pos; + // Figure out if this is a marker or a sample. + if (SUCCEEDED(hr)) { + hr = pUnk->QueryInterface(__uuidof(IMarker), (void**)&pMarker); + if (hr == E_NOINTERFACE) + hr = pUnk->QueryInterface(IID_IMFSample, (void**)&pSample); + } + + // Now handle the sample/marker appropriately. + if (SUCCEEDED(hr)) { + if (pMarker) { + hr = sendMarkerEvent(pMarker, bFlushData); + } else { + Q_ASSERT(pSample != NULL); // Not a marker, must be a sample + if (bFlushData == WriteSamples) + hr = processSampleData(pSample); + } + } + if (pMarker) + pMarker->Release(); + if (pSample) + pSample->Release(); + + if (FAILED(hr)) + break; + + pos++; + } + + clearSampleQueue(); + return hr; + } + + void beginPreroll() + { + if (m_prerolling) + return; + m_prerolling = true; + clearSampleQueue(); + clearBufferCache(); + queueEvent(MEStreamSinkRequestSample, GUID_NULL, S_OK, NULL); + } + + void endPreroll(HRESULT hrStatus) + { + if (!m_prerolling) + return; + m_prerolling = false; + queueEvent(MEStreamSinkPrerolled, GUID_NULL, hrStatus, NULL); + } + MFTIME m_prerollTargetTime; + bool m_prerolling; + + void clearSampleQueue() { + foreach (IUnknown* sample, m_sampleQueue) + sample->Release(); + m_sampleQueue.clear(); + } + + HRESULT sendMarkerEvent(IMarker *pMarker, FlushState FlushState) + { + HRESULT hr = S_OK; + HRESULT hrStatus = S_OK; // Status code for marker event. + if (FlushState == DropSamples) + hrStatus = E_ABORT; + + PROPVARIANT var; + PropVariantInit(&var); + + // Get the context data. + hr = pMarker->GetContext(&var); + + if (SUCCEEDED(hr)) + hr = queueEvent(MEStreamSinkMarker, GUID_NULL, hrStatus, &var); + + PropVariantClear(&var); + return hr; + } + + HRESULT dispatchProcessSample(AsyncOperation* pOp) + { + HRESULT hr = S_OK; + Q_ASSERT(pOp != NULL); + hr = processSamplesFromQueue(WriteSamples); + // We are in the middle of an asynchronous operation, so if something failed, send an error. + if (FAILED(hr)) + hr = queueEvent(MEError, GUID_NULL, hr, NULL); + + return hr; + } + + HRESULT dispatchFinalize(AsyncOperation*) + { + HRESULT hr = S_OK; + // Write any samples left in the queue... + hr = processSamplesFromQueue(WriteSamples); + + // Set the async status and invoke the callback. + m_finalizeResult->SetStatus(hr); + hr = MFInvokeCallback(m_finalizeResult); + return hr; + } + + HRESULT processSampleData(IMFSample *pSample) + { + LONGLONG time; + HRESULT hr = pSample->GetSampleTime(&time); + + if (m_prerolling) { + if (SUCCEEDED(hr) && time >= m_prerollTargetTime) { + IMFMediaBuffer *pBuffer = NULL; + hr = pSample->ConvertToContiguousBuffer(&pBuffer); + if (SUCCEEDED(hr)) { + SampleBuffer sb; + sb.m_buffer = pBuffer; + sb.m_time = time; + m_bufferCache.push_back(sb); + endPreroll(S_OK); + } + } else { + queueEvent(MEStreamSinkRequestSample, GUID_NULL, S_OK, NULL); + } + } else { + bool requestSample = true; + // If the plugins/multimedia/wmf/player/mfstream.cpp +time stamp is too early, just discard this sample. + if (SUCCEEDED(hr) && time >= m_startTime) { + IMFMediaBuffer *pBuffer = NULL; + hr = pSample->ConvertToContiguousBuffer(&pBuffer); + if (SUCCEEDED(hr)) { + SampleBuffer sb; + sb.m_buffer = pBuffer; + sb.m_time = time; + m_bufferCache.push_back(sb); + } + if (m_rate == 0) + requestSample = false; + } + schedulePresentation(requestSample); + } + return hr; + } + + class SampleBuffer + { + public: + IMFMediaBuffer *m_buffer; + LONGLONG m_time; + }; + QList m_bufferCache; + static const int BUFFER_CACHE_SIZE = 2; + + void clearBufferCache() + { + foreach (SampleBuffer sb, m_bufferCache) + sb.m_buffer->Release(); + m_bufferCache.clear(); + } + + void schedulePresentation(bool requestSample) + { + if (m_state == State_Paused) + return; + if (!m_scheduledBuffer) { + //get time from presentation time + MFTIME currentTime = m_startTime, sysTime; + bool timeOK = true; + if (m_rate != 0) { + if (FAILED(m_presentationClock->GetCorrelatedTime(0, ¤tTime, &sysTime))) + timeOK = false; + } + while (!m_bufferCache.isEmpty()) { + SampleBuffer sb = m_bufferCache.first(); + m_bufferCache.pop_front(); + if (timeOK && currentTime > sb.m_time) { + sb.m_buffer->Release(); + qDebug() << "currentPresentTime =" << float(currentTime / 10000) * 0.001f << " and sampleTime is" << float(sb.m_time / 10000) * 0.001f; + continue; + } + m_scheduledBuffer = sb.m_buffer; + QCoreApplication::postEvent(m_rendererControl, new PresentEvent(sb.m_time)); + if (m_rate == 0) + queueEvent(MEStreamSinkScrubSampleComplete, GUID_NULL, S_OK, NULL); + break; + } + } + if (requestSample && m_bufferCache.size() < BUFFER_CACHE_SIZE) + queueEvent(MEStreamSinkRequestSample, GUID_NULL, S_OK, NULL); + } + IMFMediaBuffer *m_scheduledBuffer; + IMFPresentationClock *m_presentationClock; + float m_rate; + }; + + BOOL MediaStream::ValidStateMatrix[MediaStream::State_Count][MediaStream::Op_Count] = + { + // States: Operations: + // SetType Start Preroll, Restart Pause Stop SetRate Sample Marker Finalize + /* NotSet */ TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, + + /* Ready */ TRUE, TRUE, TRUE, FALSE, TRUE, TRUE, TRUE, FALSE, TRUE, TRUE, + + /* Start */ FALSE, TRUE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, + + /* Pause */ FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, + + /* Stop */ FALSE, TRUE, TRUE, FALSE, FALSE, TRUE, TRUE, FALSE, TRUE, TRUE, + + /*WaitForSurfaceStart*/ FALSE, FALSE, TRUE, FALSE, FALSE, TRUE, TRUE, FALSE, FALSE, TRUE, + + /* Final */ FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE + + // Note about states: + // 1. OnClockRestart should only be called from paused state. + // 2. While paused, the sink accepts samples but does not process them. + }; + + class MediaSink : public IMFFinalizableMediaSink, public IMFClockStateSink, public IMFMediaSinkPreroll + { + public: + MediaSink(MFVideoRendererControl *rendererControl) + : m_cRef(1) + , m_shutdown(false) + , m_presentationClock(0) + , m_playRate(1) + { + m_stream = new MediaStream(this, rendererControl); + } + + ~MediaSink() + { + Q_ASSERT(m_shutdown); + } + + void setSurface(QAbstractVideoSurface *surface) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return; + m_stream->setSurface(surface); + } + + void supportedFormatsChanged() + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return; + m_stream->supportedFormatsChanged(); + } + + void present() + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return; + m_stream->present(); + } + + MFTIME getTime() + { + QMutexLocker locker(&m_mutex); + if (!m_presentationClock) + return 0; + MFTIME time, sysTime; + m_presentationClock->GetCorrelatedTime(0, &time, &sysTime); + return time; + } + + float getPlayRate() + { + QMutexLocker locker(&m_mutex); + return m_playRate; + } + + //from IUnknown + STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject) + { + if (!ppvObject) + return E_POINTER; + if (riid == IID_IMFMediaSink) { + *ppvObject = static_cast(this); + } else if (riid == IID_IMFMediaSinkPreroll) { + *ppvObject = static_cast(this); + } else if (riid == IID_IMFClockStateSink) { + *ppvObject = static_cast(this); + } else if (riid == IID_IUnknown) { + *ppvObject = static_cast(static_cast(this)); + } else { + *ppvObject = NULL; + return E_NOINTERFACE; + } + AddRef(); + return S_OK; + } + + STDMETHODIMP_(ULONG) AddRef(void) + { + return InterlockedIncrement(&m_cRef); + } + + STDMETHODIMP_(ULONG) Release(void) + { + LONG cRef = InterlockedDecrement(&m_cRef); + if (cRef == 0) + delete this; + // For thread safety, return a temporary variable. + return cRef; + } + + + + //IMFMediaSinkPreroll + STDMETHODIMP NotifyPreroll(MFTIME hnsUpcomingStartTime) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + return m_stream->startPreroll(hnsUpcomingStartTime); + } + + //from IMFFinalizableMediaSink + STDMETHODIMP BeginFinalize(IMFAsyncCallback *pCallback, IUnknown *punkState) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + return m_stream->finalize(pCallback, punkState); + } + + STDMETHODIMP EndFinalize(IMFAsyncResult *pResult) + { + HRESULT hr = S_OK; + // Return the status code from the async result. + if (pResult == NULL) + hr = E_INVALIDARG; + else + hr = pResult->GetStatus(); + return hr; + } + + //from IMFMediaSink + STDMETHODIMP GetCharacteristics( + DWORD *pdwCharacteristics) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + *pdwCharacteristics = MEDIASINK_FIXED_STREAMS | MEDIASINK_CAN_PREROLL; + return S_OK; + } + + STDMETHODIMP AddStreamSink( + DWORD, + IMFMediaType *, + IMFStreamSink **) + { + QMutexLocker locker(&m_mutex); + return m_shutdown ? MF_E_SHUTDOWN : MF_E_STREAMSINKS_FIXED; + } + + STDMETHODIMP RemoveStreamSink( + DWORD) + { + QMutexLocker locker(&m_mutex); + return m_shutdown ? MF_E_SHUTDOWN : MF_E_STREAMSINKS_FIXED; + } + + STDMETHODIMP GetStreamSinkCount( + DWORD *pcStreamSinkCount) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + *pcStreamSinkCount = 1; + return S_OK; + } + + STDMETHODIMP GetStreamSinkByIndex( + DWORD dwIndex, + IMFStreamSink **ppStreamSink) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + + if (dwIndex != 0) + return MF_E_INVALIDINDEX; + + *ppStreamSink = m_stream; + m_stream->AddRef(); + return S_OK; + } + + STDMETHODIMP GetStreamSinkById( + DWORD dwStreamSinkIdentifier, + IMFStreamSink **ppStreamSink) + { + if (ppStreamSink == NULL) + return E_INVALIDARG; + if (dwStreamSinkIdentifier != MediaStream::DEFAULT_MEDIA_STREAM_ID) + return MF_E_INVALIDSTREAMNUMBER; + + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + + *ppStreamSink = m_stream; + m_stream->AddRef(); + return S_OK; + } + + STDMETHODIMP SetPresentationClock( + IMFPresentationClock *pPresentationClock) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + + if (m_presentationClock) { + m_presentationClock->RemoveClockStateSink(this); + m_presentationClock->Release(); + } + m_presentationClock = pPresentationClock; + if (m_presentationClock) { + m_presentationClock->AddRef(); + m_presentationClock->AddClockStateSink(this); + } + m_stream->setClock(m_presentationClock); + return S_OK; + } + + STDMETHODIMP GetPresentationClock( + IMFPresentationClock **ppPresentationClock) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + *ppPresentationClock = m_presentationClock; + if (m_presentationClock) { + m_presentationClock->AddRef(); + return S_OK; + } + return MF_E_NO_CLOCK; + } + + STDMETHODIMP Shutdown(void) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + + m_stream->shutdown(); + if (m_presentationClock) { + m_presentationClock->Release(); + m_presentationClock = NULL; + } + m_stream->Release(); + m_stream = NULL; + m_shutdown = true; + return S_OK; + } + + // IMFClockStateSink methods + STDMETHODIMP OnClockStart(MFTIME, LONGLONG llClockStartOffset) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + return m_stream->start(llClockStartOffset); + } + + STDMETHODIMP OnClockStop(MFTIME) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + return m_stream->stop(); + } + + STDMETHODIMP OnClockPause(MFTIME) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + return m_stream->pause(); + } + + STDMETHODIMP OnClockRestart(MFTIME) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + return m_stream->restart(); + } + + STDMETHODIMP OnClockSetRate(MFTIME, float flRate) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + m_playRate = flRate; + return m_stream->setRate(flRate); + } + + private: + long m_cRef; + QMutex m_mutex; + bool m_shutdown; + IMFPresentationClock *m_presentationClock; + MediaStream *m_stream; + float m_playRate; + }; + + class VideoRendererActivate : public IMFActivate + { + public: + VideoRendererActivate(MFVideoRendererControl *rendererControl) + : m_cRef(1) + , m_sink(0) + , m_rendererControl(rendererControl) + , m_attributes(0) + { + 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(this); + } else if (riid == IID_IMFAttributes) { + *ppvObject = static_cast(this); + } else if (riid == IID_IUnknown) { + *ppvObject = static_cast(static_cast(this)); + } else { + *ppvObject = NULL; + return E_NOINTERFACE; + } + AddRef(); + return S_OK; + } + + STDMETHODIMP_(ULONG) AddRef(void) + { + return InterlockedIncrement(&m_cRef); + } + + STDMETHODIMP_(ULONG) Release(void) + { + LONG cRef = InterlockedDecrement(&m_cRef); + if (cRef == 0) + delete this; + // For thread safety, return a temporary variable. + return cRef; + } + + //from IMFActivate + STDMETHODIMP ActivateObject(REFIID riid, void **ppv) + { + if (!ppv) + return E_INVALIDARG; + QMutexLocker locker(&m_mutex); + if (!m_sink) { + m_sink = new MediaSink(m_rendererControl); + if (m_surface) + m_sink->setSurface(m_surface); + } + return m_sink->QueryInterface(riid, ppv); + } + + STDMETHODIMP ShutdownObject(void) + { + QMutexLocker locker(&m_mutex); + HRESULT hr = S_OK; + if (m_sink) { + hr = m_sink->Shutdown(); + m_sink->Release(); + m_sink = NULL; + } + return hr; + } + + STDMETHODIMP DetachObject(void) + { + QMutexLocker locker(&m_mutex); + if (m_sink) { + m_sink->Release(); + m_sink = NULL; + } + return S_OK; + } + + //from IMFAttributes + STDMETHODIMP GetItem( + REFGUID guidKey, + PROPVARIANT *pValue) + { + return m_attributes->GetItem(guidKey, pValue); + } + + STDMETHODIMP GetItemType( + REFGUID guidKey, + MF_ATTRIBUTE_TYPE *pType) + { + return m_attributes->GetItemType(guidKey, pType); + } + + STDMETHODIMP CompareItem( + REFGUID guidKey, + REFPROPVARIANT Value, + BOOL *pbResult) + { + return m_attributes->CompareItem(guidKey, Value, pbResult); + } + + STDMETHODIMP Compare( + IMFAttributes *pTheirs, + MF_ATTRIBUTES_MATCH_TYPE MatchType, + BOOL *pbResult) + { + return m_attributes->Compare(pTheirs, MatchType, pbResult); + } + + STDMETHODIMP GetUINT32( + REFGUID guidKey, + UINT32 *punValue) + { + return m_attributes->GetUINT32(guidKey, punValue); + } + + STDMETHODIMP GetUINT64( + REFGUID guidKey, + UINT64 *punValue) + { + return m_attributes->GetUINT64(guidKey, punValue); + } + + STDMETHODIMP GetDouble( + REFGUID guidKey, + double *pfValue) + { + return m_attributes->GetDouble(guidKey, pfValue); + } + + STDMETHODIMP GetGUID( + REFGUID guidKey, + GUID *pguidValue) + { + return m_attributes->GetGUID(guidKey, pguidValue); + } + + STDMETHODIMP GetStringLength( + REFGUID guidKey, + UINT32 *pcchLength) + { + return m_attributes->GetStringLength(guidKey, pcchLength); + } + + STDMETHODIMP GetString( + REFGUID guidKey, + LPWSTR pwszValue, + UINT32 cchBufSize, + UINT32 *pcchLength) + { + return m_attributes->GetString(guidKey, pwszValue, cchBufSize, pcchLength); + } + + STDMETHODIMP GetAllocatedString( + REFGUID guidKey, + LPWSTR *ppwszValue, + UINT32 *pcchLength) + { + return m_attributes->GetAllocatedString(guidKey, ppwszValue, pcchLength); + } + + STDMETHODIMP GetBlobSize( + REFGUID guidKey, + UINT32 *pcbBlobSize) + { + return m_attributes->GetBlobSize(guidKey, pcbBlobSize); + } + + STDMETHODIMP GetBlob( + REFGUID guidKey, + UINT8 *pBuf, + UINT32 cbBufSize, + UINT32 *pcbBlobSize) + { + return m_attributes->GetBlob(guidKey, pBuf, cbBufSize, pcbBlobSize); + } + + STDMETHODIMP GetAllocatedBlob( + REFGUID guidKey, + UINT8 **ppBuf, + UINT32 *pcbSize) + { + return m_attributes->GetAllocatedBlob(guidKey, ppBuf, pcbSize); + } + + STDMETHODIMP GetUnknown( + REFGUID guidKey, + REFIID riid, + LPVOID *ppv) + { + return m_attributes->GetUnknown(guidKey, riid, ppv); + } + + STDMETHODIMP SetItem( + REFGUID guidKey, + REFPROPVARIANT Value) + { + return m_attributes->SetItem(guidKey, Value); + } + + STDMETHODIMP DeleteItem( + REFGUID guidKey) + { + return m_attributes->DeleteItem(guidKey); + } + + STDMETHODIMP DeleteAllItems(void) + { + return m_attributes->DeleteAllItems(); + } + + STDMETHODIMP SetUINT32( + REFGUID guidKey, + UINT32 unValue) + { + return m_attributes->SetUINT32(guidKey, unValue); + } + + STDMETHODIMP SetUINT64( + REFGUID guidKey, + UINT64 unValue) + { + return m_attributes->SetUINT64(guidKey, unValue); + } + + STDMETHODIMP SetDouble( + REFGUID guidKey, + double fValue) + { + return m_attributes->SetDouble(guidKey, fValue); + } + + STDMETHODIMP SetGUID( + REFGUID guidKey, + REFGUID guidValue) + { + return m_attributes->SetGUID(guidKey, guidValue); + } + + STDMETHODIMP SetString( + REFGUID guidKey, + LPCWSTR wszValue) + { + return m_attributes->SetString(guidKey, wszValue); + } + + STDMETHODIMP SetBlob( + REFGUID guidKey, + const UINT8 *pBuf, + UINT32 cbBufSize) + { + return m_attributes->SetBlob(guidKey, pBuf, cbBufSize); + } + + STDMETHODIMP SetUnknown( + REFGUID guidKey, + IUnknown *pUnknown) + { + return m_attributes->SetUnknown(guidKey, pUnknown); + } + + STDMETHODIMP LockStore(void) + { + return m_attributes->LockStore(); + } + + STDMETHODIMP UnlockStore(void) + { + return m_attributes->UnlockStore(); + } + + STDMETHODIMP GetCount( + UINT32 *pcItems) + { + return m_attributes->GetCount(pcItems); + } + + STDMETHODIMP GetItemByIndex( + UINT32 unIndex, + GUID *pguidKey, + PROPVARIANT *pValue) + { + return m_attributes->GetItemByIndex(unIndex, pguidKey, pValue); + } + + STDMETHODIMP CopyAllItems( + IMFAttributes *pDest) + { + return m_attributes->CopyAllItems(pDest); + } + + ///////////////////////////////// + void setSurface(QAbstractVideoSurface *surface) + { + QMutexLocker locker(&m_mutex); + if (m_surface == surface) + return; + + m_surface = surface; + + if (!m_sink) + return; + m_sink->setSurface(m_surface); + } + + void supportedFormatsChanged() + { + QMutexLocker locker(&m_mutex); + if (!m_sink) + return; + m_sink->supportedFormatsChanged(); + } + + void present() + { + QMutexLocker locker(&m_mutex); + if (!m_sink) + return; + m_sink->present(); + } + + MFTIME getTime() + { + if (m_sink) + return m_sink->getTime(); + return 0; + } + + float getPlayRate() + { + if (m_sink) + return m_sink->getPlayRate(); + return 1; + } + + private: + long m_cRef; + bool m_shutdown; + MediaSink *m_sink; + MFVideoRendererControl *m_rendererControl; + IMFAttributes *m_attributes; + QAbstractVideoSurface *m_surface; + QMutex m_mutex; + }; +} + +MFVideoRendererControl::MFVideoRendererControl(QObject *parent) + : QVideoRendererControl(parent) + , m_surface(0) + , m_callback(0) +{ + m_currentActivate = new VideoRendererActivate(this); +} + +MFVideoRendererControl::~MFVideoRendererControl() +{ + if (m_currentActivate) { + m_currentActivate->ShutdownObject(); + m_currentActivate->Release(); + } +} + +QAbstractVideoSurface *MFVideoRendererControl::surface() const +{ + return m_surface; +} + +void MFVideoRendererControl::setSurface(QAbstractVideoSurface *surface) +{ + if (m_surface == surface) + return; + + 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())); + } + static_cast(m_currentActivate)->setSurface(m_surface); +} + +void MFVideoRendererControl::customEvent(QEvent *event) +{ + if (event->type() == MediaStream::PresentSurface) { + MFTIME targetTime = static_cast(event)->targetTime(); + MFTIME currentTime = static_cast(m_currentActivate)->getTime(); + float playRate = static_cast(m_currentActivate)->getPlayRate(); + if (playRate > 0.0001f && targetTime > currentTime) + QTimer::singleShot(int((float)((targetTime - currentTime) / 10000) / playRate), this, SLOT(present())); + else + present(); + return; + } + QChildEvent *childEvent = dynamic_cast(event); + if (!childEvent) { + QObject::customEvent(event); + return; + } + static_cast(childEvent->child())->customEvent(event); +} + +void MFVideoRendererControl::supportedFormatsChanged() +{ + static_cast(m_currentActivate)->supportedFormatsChanged(); +} + +void MFVideoRendererControl::present() +{ + static_cast(m_currentActivate)->present(); +} + +IMFActivate* MFVideoRendererControl::currentActivate() const +{ + return m_currentActivate; +} + +#include "moc_mfvideorenderercontrol.cpp" +#include "mfvideorenderercontrol.moc" diff --git a/src/plugins/wmf/player/mfvideorenderercontrol.h b/src/plugins/wmf/player/mfvideorenderercontrol.h new file mode 100644 index 000000000..f34b5bf95 --- /dev/null +++ b/src/plugins/wmf/player/mfvideorenderercontrol.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef MFVIDEORENDERERCONTROL_H +#define MFVIDEORENDERERCONTROL_H + +#include "../../src/multimedia/qvideorenderercontrol.h" +#include +#include + +QT_USE_NAMESPACE + +class MFVideoRendererControl : public QVideoRendererControl +{ + Q_OBJECT +public: + MFVideoRendererControl(QObject *parent = 0); + ~MFVideoRendererControl(); + + QAbstractVideoSurface *surface() const; + void setSurface(QAbstractVideoSurface *surface); + + IMFActivate* currentActivate() const; + +protected: + void customEvent(QEvent *event); + +private Q_SLOTS: + void supportedFormatsChanged(); + void present(); + +private: + QAbstractVideoSurface *m_surface; + IMFActivate *m_currentActivate; + IMFSampleGrabberSinkCallback *m_callback; +}; + +#endif diff --git a/src/plugins/wmf/player/player.pri b/src/plugins/wmf/player/player.pri new file mode 100644 index 000000000..58375ba44 --- /dev/null +++ b/src/plugins/wmf/player/player.pri @@ -0,0 +1,30 @@ +INCLUDEPATH += $$PWD + +LIBS += -lstrmiids -ldmoguids -luuid -lmsdmo -lole32 -loleaut32 -lMf -lMfuuid -lMfplat -lPropsys + +DEFINES += QMEDIA_MEDIAFOUNDATION_PLAYER + +HEADERS += \ + $$PWD/mfplayerservice.h \ + $$PWD/mfplayersession.h \ + $$PWD/mfstream.h \ + $$PWD/sourceresolver.h \ + $$PWD/mfplayercontrol.h \ + $$PWD/mfvideorenderercontrol.h \ + $$PWD/mfaudioendpointcontrol.h \ + $$PWD/mfmetadatacontrol.h + +SOURCES += \ + $$PWD/mfplayerservice.cpp \ + $$PWD/mfplayersession.cpp \ + $$PWD/mfstream.cpp \ + $$PWD/sourceresolver.cpp \ + $$PWD/mfplayercontrol.cpp \ + $$PWD/mfvideorenderercontrol.cpp \ + $$PWD/mfaudioendpointcontrol.cpp \ + $$PWD/mfmetadatacontrol.cpp + +!simulator { + HEADERS += $$PWD/evr9videowindowcontrol.h + SOURCES += $$PWD/evr9videowindowcontrol.cpp +} diff --git a/src/plugins/wmf/player/sourceresolver.cpp b/src/plugins/wmf/player/sourceresolver.cpp new file mode 100644 index 000000000..1b98365b5 --- /dev/null +++ b/src/plugins/wmf/player/sourceresolver.cpp @@ -0,0 +1,318 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "mfplayersession.h" +#include "mfstream.h" +#include "sourceresolver.h" +#include +#include +#include + +/* + SourceResolver is separated from MFPlayerSession to handle the work of resolving a media source + asynchronously. You call SourceResolver::load to request resolving a media source asynchronously, + and it will emit mediaSourceReady() when resolving is done. You can call SourceResolver::cancel to + stop the previous load operation if there is any. +*/ + +SourceResolver::SourceResolver(QObject *parent) + : QObject(parent) + , m_cRef(1) + , m_cancelCookie(0) + , m_sourceResolver(0) + , m_mediaSource(0) + , m_stream(0) +{ +} + +SourceResolver::~SourceResolver() +{ + shutdown(); + if (m_cancelCookie) + m_cancelCookie->Release(); + if (m_sourceResolver) + m_sourceResolver->Release(); +} + +STDMETHODIMP SourceResolver::QueryInterface(REFIID riid, LPVOID *ppvObject) +{ + if (!ppvObject) + return E_POINTER; + if (riid == IID_IUnknown) { + *ppvObject = static_cast(this); + } else if (riid == IID_IMFAsyncCallback) { + *ppvObject = static_cast(this); + } else { + *ppvObject = NULL; + return E_NOINTERFACE; + } + AddRef(); + return S_OK; +} + +STDMETHODIMP_(ULONG) SourceResolver::AddRef(void) +{ + return InterlockedIncrement(&m_cRef); +} + +STDMETHODIMP_(ULONG) SourceResolver::Release(void) +{ + LONG cRef = InterlockedDecrement(&m_cRef); + if (cRef == 0) + this->deleteLater(); + return cRef; +} + +HRESULT STDMETHODCALLTYPE SourceResolver::Invoke(IMFAsyncResult *pAsyncResult) +{ + QMutexLocker locker(&m_mutex); + MF_OBJECT_TYPE ObjectType = MF_OBJECT_INVALID; + IUnknown* pSource = NULL; + State *state = static_cast(pAsyncResult->GetStateNoAddRef()); + + HRESULT hr = S_OK; + if (state->fromStream()) + hr = m_sourceResolver->EndCreateObjectFromByteStream(pAsyncResult, &ObjectType, &pSource); + else + hr = m_sourceResolver->EndCreateObjectFromURL(pAsyncResult, &ObjectType, &pSource); + + if (state->sourceResolver() != m_sourceResolver) { + //This is a cancelled one + return S_OK; + } + + if (m_cancelCookie) { + m_cancelCookie->Release(); + m_cancelCookie = NULL; + } + + if (FAILED(hr)) { + emit error(hr); + return S_OK; + } + + if (m_mediaSource) { + m_mediaSource->Release(); + m_mediaSource = NULL; + } + + hr = pSource->QueryInterface(IID_PPV_ARGS(&m_mediaSource)); + if (FAILED(hr)) { + emit error(hr); + return S_OK; + } + + emit mediaSourceReady(); + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE SourceResolver::GetParameters(DWORD*, DWORD*) +{ + return E_NOTIMPL; +} + +void SourceResolver::load(QMediaResourceList& resources, QIODevice* stream) +{ + QMutexLocker locker(&m_mutex); + HRESULT hr = S_OK; + if (!m_sourceResolver) + hr = MFCreateSourceResolver(&m_sourceResolver); + + if (m_stream) { + m_stream->Release(); + m_stream = NULL; + } + + if (FAILED(hr)) { + qWarning() << "Failed to create Source Resolver!"; + emit error(hr); + } else if (stream) { + if (resources.count() > 0) { + QMediaResource resource = resources.takeFirst(); + QUrl url = resource.url(); + m_stream = new MFStream(stream, false); + hr = m_sourceResolver->BeginCreateObjectFromByteStream(m_stream, reinterpret_cast(url.toString().utf16()), + MF_RESOLUTION_MEDIASOURCE, NULL, &m_cancelCookie, this, new State(m_sourceResolver, true)); + if (FAILED(hr)) { + qWarning() << "Unsupported stream!"; + emit error(hr); + } + } else { + hr = MF_E_UNSUPPORTED_BYTESTREAM_TYPE; + qWarning() << "Can't load stream without a hint of MIME type in a url"; + emit error(hr); + } + } else { + QMediaResource resource = resources.takeFirst(); + QUrl url = resource.url(); +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "loading :" << url; + qDebug() << "url path =" << url.path().mid(1); +#endif +#ifdef TEST_STREAMING + //Testing stream function + if (url.scheme() == QLatin1String("file")) { + stream = new QFile(url.path().mid(1), this); + if (stream->open(QIODevice::ReadOnly)) { + m_stream = new MFStream(stream, true); + hr = m_sourceResolver->BeginCreateObjectFromByteStream(m_stream, reinterpret_cast(url.toString().utf16()), + MF_RESOLUTION_MEDIASOURCE, NULL, &m_cancelCookie, this, new State(m_sourceResolver, true)); + if (FAILED(hr)) { + qWarning() << "Unsupported stream!"; + emit error(hr); + } + } else { + delete stream; + emit error(QMediaPlayer::FormatError); + } + } else +#endif + if (url.scheme() == QLatin1String("qrc")) { + // If the canonical URL refers to a Qt resource, open with QFile and use + // the stream playback capability to play. + stream = new QFile(QLatin1Char(':') + url.path(), this); + if (stream->open(QIODevice::ReadOnly)) { + m_stream = new MFStream(stream, true); + hr = m_sourceResolver->BeginCreateObjectFromByteStream(m_stream, reinterpret_cast(url.toString().utf16()), + MF_RESOLUTION_MEDIASOURCE, NULL, &m_cancelCookie, this, new State(m_sourceResolver, true)); + if (FAILED(hr)) { + qWarning() << "Unsupported stream!"; + emit error(hr); + } + } else { + delete stream; + emit error(QMediaPlayer::FormatError); + } + } else { + hr = m_sourceResolver->BeginCreateObjectFromURL(reinterpret_cast(url.toString().utf16()), + MF_RESOLUTION_MEDIASOURCE, NULL, &m_cancelCookie, this, new State(m_sourceResolver, false)); + if (FAILED(hr)) { + qWarning() << "Unsupported url scheme!"; + emit error(hr); + } + } + } +} + +void SourceResolver::cancel() +{ + QMutexLocker locker(&m_mutex); + if (m_cancelCookie) { + m_sourceResolver->CancelObjectCreation(m_cancelCookie); + m_cancelCookie->Release(); + m_cancelCookie = NULL; + m_sourceResolver->Release(); + m_sourceResolver = NULL; + } +} + +void SourceResolver::shutdown() +{ + if (m_mediaSource) { + m_mediaSource->Shutdown(); + m_mediaSource->Release(); + m_mediaSource = NULL; + } + + if (m_stream) { + m_stream->Release(); + m_stream = NULL; + } +} + +IMFMediaSource* SourceResolver::mediaSource() const +{ + return m_mediaSource; +} + +///////////////////////////////////////////////////////////////////////////////// +SourceResolver::State::State(IMFSourceResolver *sourceResolver, bool fromStream) + : m_cRef(1) + , m_sourceResolver(sourceResolver) + , m_fromStream(fromStream) +{ + sourceResolver->AddRef(); +} + +SourceResolver::State::~State() +{ + m_sourceResolver->Release(); +} + +STDMETHODIMP SourceResolver::State::QueryInterface(REFIID riid, LPVOID *ppvObject) +{ + if (!ppvObject) + return E_POINTER; + if (riid == IID_IUnknown) { + *ppvObject = static_cast(this); + } else { + *ppvObject = NULL; + return E_NOINTERFACE; + } + AddRef(); + return S_OK; +} + +STDMETHODIMP_(ULONG) SourceResolver::State::AddRef(void) +{ + return InterlockedIncrement(&m_cRef); +} + +STDMETHODIMP_(ULONG) SourceResolver::State::Release(void) +{ + LONG cRef = InterlockedDecrement(&m_cRef); + if (cRef == 0) + delete this; + // For thread safety, return a temporary variable. + return cRef; +} + +IMFSourceResolver* SourceResolver::State::sourceResolver() const +{ + return m_sourceResolver; +} + +bool SourceResolver::State::fromStream() const +{ + return m_fromStream; +} + diff --git a/src/plugins/wmf/player/sourceresolver.h b/src/plugins/wmf/player/sourceresolver.h new file mode 100644 index 000000000..60bee69f1 --- /dev/null +++ b/src/plugins/wmf/player/sourceresolver.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef SOURCERESOLVER_H +#define SOURCERESOLVER_H + +#include "mfstream.h" +#include "qmediaresource.h" + +class SourceResolver: public QObject, public IMFAsyncCallback +{ + Q_OBJECT +public: + SourceResolver(QObject *parent); + + ~SourceResolver(); + + STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject); + STDMETHODIMP_(ULONG) AddRef(void); + STDMETHODIMP_(ULONG) Release(void); + + HRESULT STDMETHODCALLTYPE Invoke(IMFAsyncResult *pAsyncResult); + + HRESULT STDMETHODCALLTYPE GetParameters(DWORD*, DWORD*); + + void load(QMediaResourceList& resources, QIODevice* stream); + + void cancel(); + + void shutdown(); + + IMFMediaSource* mediaSource() const; + +Q_SIGNALS: + void error(long hr); + void mediaSourceReady(); + +private: + class State : public IUnknown + { + public: + State(IMFSourceResolver *sourceResolver, bool fromStream); + ~State(); + + STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject); + + STDMETHODIMP_(ULONG) AddRef(void); + + STDMETHODIMP_(ULONG) Release(void); + + IMFSourceResolver* sourceResolver() const; + bool fromStream() const; + + private: + long m_cRef; + IMFSourceResolver *m_sourceResolver; + bool m_fromStream; + }; + + long m_cRef; + IUnknown *m_cancelCookie; + IMFSourceResolver *m_sourceResolver; + IMFMediaSource *m_mediaSource; + MFStream *m_stream; + QMutex m_mutex; +}; + +#endif diff --git a/src/plugins/wmf/wmf.pro b/src/plugins/wmf/wmf.pro new file mode 100644 index 000000000..5eccc80d5 --- /dev/null +++ b/src/plugins/wmf/wmf.pro @@ -0,0 +1,16 @@ +load(qt_module) + +TARGET = wmfengine +QT += multimediakit-private network +PLUGIN_TYPE=mediaservice + +load(qt_plugin) + +DESTDIR = $$QT.multimediakit.plugins/$${PLUGIN_TYPE} + +DEPENDPATH += . + +HEADERS += wmfserviceplugin.h +SOURCES += wmfserviceplugin.cpp + +include (player/player.pri) diff --git a/src/plugins/wmf/wmfserviceplugin.cpp b/src/plugins/wmf/wmfserviceplugin.cpp new file mode 100644 index 000000000..31910fd3a --- /dev/null +++ b/src/plugins/wmf/wmfserviceplugin.cpp @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include + +#include "wmfserviceplugin.h" +#ifdef QMEDIA_MEDIAFOUNDATION_PLAYER +#include "mfplayerservice.h" +#endif +#include + +QStringList WMFServicePlugin::keys() const +{ + return QStringList() +#ifdef QMEDIA_MEDIAFOUNDATION_PLAYER + << QLatin1String(Q_MEDIASERVICE_MEDIAPLAYER) +#endif + ; +} + +QMediaService* WMFServicePlugin::create(QString const& key) +{ +#ifdef QMEDIA_MEDIAFOUNDATION_PLAYER + if (key == QLatin1String(Q_MEDIASERVICE_MEDIAPLAYER)) + return new MFPlayerService; +#endif + + //qDebug() << "unsupported key:" << key; + return 0; +} + +void WMFServicePlugin::release(QMediaService *service) +{ + delete service; +} + +QMediaServiceProviderHint::Features WMFServicePlugin::supportedFeatures( + const QByteArray &service) const +{ + if (service == Q_MEDIASERVICE_MEDIAPLAYER) + return QMediaServiceProviderHint::StreamPlayback; + else + return QMediaServiceProviderHint::Features(); +} + +QList WMFServicePlugin::devices(const QByteArray &service) const +{ + return QList(); +} + +QString WMFServicePlugin::deviceDescription(const QByteArray &service, const QByteArray &device) +{ + return QString(); +} + +Q_EXPORT_PLUGIN2(qtmedia_wmfengine, WMFServicePlugin); + diff --git a/src/plugins/wmf/wmfserviceplugin.h b/src/plugins/wmf/wmfserviceplugin.h new file mode 100644 index 000000000..cfaaef58c --- /dev/null +++ b/src/plugins/wmf/wmfserviceplugin.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef WMFSERVICEPLUGIN_H +#define WMFSERVICEPLUGIN_H + +#include "qmediaserviceproviderplugin.h" + +QT_USE_NAMESPACE + +class WMFServicePlugin + : public QMediaServiceProviderPlugin + , public QMediaServiceSupportedDevicesInterface + , public QMediaServiceFeaturesInterface +{ + Q_OBJECT + Q_INTERFACES(QMediaServiceSupportedDevicesInterface) + Q_INTERFACES(QMediaServiceFeaturesInterface) +public: + QStringList keys() const; + QMediaService* create(QString const& key); + void release(QMediaService *service); + + QMediaServiceProviderHint::Features supportedFeatures(const QByteArray &service) const; + + QList devices(const QByteArray &service) const; + QString deviceDescription(const QByteArray &service, const QByteArray &device); +}; + +#endif // DSSERVICEPLUGIN_H diff --git a/sync.profile b/sync.profile index 6370cb1cf..9939e4638 100644 --- a/sync.profile +++ b/sync.profile @@ -38,6 +38,7 @@ "directshow" => {}, "wmsdk" => {}, "wmp" => {}, + "wmf" => {}, "evr" => {}, # Linux tests -- cgit v1.2.3