From 778b233bcdbc2942a44efd468f6bfb4c0ce5d5cf Mon Sep 17 00:00:00 2001 From: Andrew Knight Date: Thu, 21 Aug 2014 10:55:10 +0300 Subject: winrt: Add media playback service This introduces a MediaPlayer service based on the MFEngineEx API. Only scene graph video rendering is supported at this time. The video renderer control is abstracted in order to provide a base for future video nodes which do not source their content from the MF engine. [ChangeLog] Media player support was added to the winrt backend. Change-Id: I8155a1030466ea352fad0a87d1ae97a88983760c Reviewed-by: Yoann Lopes --- .../winrt/qwinrtabstractvideorenderercontrol.cpp | 403 +++++++++ .../winrt/qwinrtabstractvideorenderercontrol.h | 87 ++ src/plugins/winrt/qwinrtmediaplayercontrol.cpp | 899 +++++++++++++++++++++ src/plugins/winrt/qwinrtmediaplayercontrol.h | 104 +++ src/plugins/winrt/qwinrtmediaplayerservice.cpp | 111 +++ src/plugins/winrt/qwinrtmediaplayerservice.h | 67 ++ src/plugins/winrt/qwinrtplayerrenderercontrol.cpp | 156 ++++ src/plugins/winrt/qwinrtplayerrenderercontrol.h | 72 ++ src/plugins/winrt/qwinrtserviceplugin.cpp | 9 + src/plugins/winrt/winrt.json | 2 +- src/plugins/winrt/winrt.pro | 8 + 11 files changed, 1917 insertions(+), 1 deletion(-) create mode 100644 src/plugins/winrt/qwinrtabstractvideorenderercontrol.cpp create mode 100644 src/plugins/winrt/qwinrtabstractvideorenderercontrol.h create mode 100644 src/plugins/winrt/qwinrtmediaplayercontrol.cpp create mode 100644 src/plugins/winrt/qwinrtmediaplayercontrol.h create mode 100644 src/plugins/winrt/qwinrtmediaplayerservice.cpp create mode 100644 src/plugins/winrt/qwinrtmediaplayerservice.h create mode 100644 src/plugins/winrt/qwinrtplayerrenderercontrol.cpp create mode 100644 src/plugins/winrt/qwinrtplayerrenderercontrol.h (limited to 'src/plugins/winrt') diff --git a/src/plugins/winrt/qwinrtabstractvideorenderercontrol.cpp b/src/plugins/winrt/qwinrtabstractvideorenderercontrol.cpp new file mode 100644 index 000000000..175fec1d5 --- /dev/null +++ b/src/plugins/winrt/qwinrtabstractvideorenderercontrol.cpp @@ -0,0 +1,403 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwinrtabstractvideorenderercontrol.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define EGL_EGLEXT_PROTOTYPES +#include +#include + +#include +#include +#include + +using namespace Microsoft::WRL; + +QT_USE_NAMESPACE + +#define BREAK_IF_FAILED(msg) RETURN_IF_FAILED(msg, break) +#define CONTINUE_IF_FAILED(msg) RETURN_IF_FAILED(msg, continue) + +// Global D3D device to be shared between video surfaces +struct QWinRTVideoRendererControlGlobal +{ + QWinRTVideoRendererControlGlobal() + { + HRESULT hr; + + D3D_FEATURE_LEVEL featureLevels[] = + { + D3D_FEATURE_LEVEL_11_1, + D3D_FEATURE_LEVEL_11_0, + D3D_FEATURE_LEVEL_10_1, + D3D_FEATURE_LEVEL_10_0, + D3D_FEATURE_LEVEL_9_3, + D3D_FEATURE_LEVEL_9_2, + D3D_FEATURE_LEVEL_9_1 + }; + + UINT flags = D3D11_CREATE_DEVICE_VIDEO_SUPPORT; +#ifdef _DEBUG + flags |= D3D11_CREATE_DEVICE_DEBUG; +#endif + hr = D3D11CreateDevice(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, flags, + featureLevels, ARRAYSIZE(featureLevels), D3D11_SDK_VERSION, + &device, &featureLevel, &context); + if (FAILED(hr)) + qErrnoWarning(hr, "Failed to create D3D device"); + + if (!device || FAILED(hr)) { + hr = D3D11CreateDevice(NULL, D3D_DRIVER_TYPE_WARP, NULL, flags, + featureLevels, ARRAYSIZE(featureLevels), D3D11_SDK_VERSION, + &device, &featureLevel, &context); + if (FAILED(hr)) { + qErrnoWarning(hr, "Failed to create software D3D device"); + return; + } + } + + ComPtr multithread; + hr = device.As(&multithread); + Q_ASSERT_SUCCEEDED(hr); + hr = multithread->SetMultithreadProtected(true); + Q_ASSERT_SUCCEEDED(hr); + + ComPtr dxgiDevice; + hr = device.As(&dxgiDevice); + Q_ASSERT_SUCCEEDED(hr); + ComPtr adapter; + hr = dxgiDevice->GetAdapter(&adapter); + Q_ASSERT_SUCCEEDED(hr); + hr = adapter->EnumOutputs(0, &output); + Q_ASSERT_SUCCEEDED(hr); + } + + ComPtr device; + ComPtr context; + D3D_FEATURE_LEVEL featureLevel; + ComPtr output; +}; +Q_GLOBAL_STATIC(QWinRTVideoRendererControlGlobal, g) + +class QWinRTVideoBuffer : public QAbstractVideoBuffer, public QOpenGLTexture +{ +public: + QWinRTVideoBuffer() + : QAbstractVideoBuffer(QAbstractVideoBuffer::GLTextureHandle) + , QOpenGLTexture(QOpenGLTexture::Target2D) + { + } + + void addRef() + { + refCount.ref(); + } + + void release() Q_DECL_OVERRIDE + { + if (!refCount.deref()) + delete this; + } + + MapMode mapMode() const Q_DECL_OVERRIDE + { + return NotMapped; + } + + uchar *map(MapMode mode, int *numBytes, int *bytesPerLine) Q_DECL_OVERRIDE + { + Q_UNUSED(mode); + Q_UNUSED(numBytes); + Q_UNUSED(bytesPerLine); + return 0; + } + + void unmap() Q_DECL_OVERRIDE + { + } + + QVariant handle() const Q_DECL_OVERRIDE + { + return QVariant::fromValue(textureId()); + } + +private: + QAtomicInt refCount; +}; + +enum DirtyState { + NotDirty, // All resources have been created + TextureDirty, // The shared D3D texture needs to be recreated + SurfaceDirty // The shared EGL surface needs to be recreated +}; + +class QWinRTAbstractVideoRendererControlPrivate +{ +public: + QAbstractVideoSurface *surface; + QVideoSurfaceFormat format; + + DirtyState dirtyState; + + HANDLE shareHandle; + ComPtr texture; + + EGLDisplay eglDisplay; + EGLConfig eglConfig; + EGLSurface eglSurface; + + QWinRTVideoBuffer *videoBuffer; + + QThread renderThread; + bool active; +}; + +ID3D11Device *QWinRTAbstractVideoRendererControl::d3dDevice() +{ + return g->device.Get(); +} + +// This is required so that subclasses can stop the render thread before deletion +void QWinRTAbstractVideoRendererControl::shutdown() +{ + Q_D(QWinRTAbstractVideoRendererControl); + if (d->renderThread.isRunning()) { + d->renderThread.requestInterruption(); + d->renderThread.wait(); + } +} + +QWinRTAbstractVideoRendererControl::QWinRTAbstractVideoRendererControl(const QSize &size, QObject *parent) + : QVideoRendererControl(parent), d_ptr(new QWinRTAbstractVideoRendererControlPrivate) +{ + Q_D(QWinRTAbstractVideoRendererControl); + + d->surface = Q_NULLPTR; + d->format = QVideoSurfaceFormat(size, QVideoFrame::Format_BGRA32, + QAbstractVideoBuffer::GLTextureHandle); + d->dirtyState = TextureDirty; + d->shareHandle = 0; + d->eglDisplay = EGL_NO_DISPLAY; + d->eglConfig = 0; + d->eglSurface = EGL_NO_SURFACE; + d->active = false; + + d->videoBuffer = new QWinRTVideoBuffer; + + connect(&d->renderThread, &QThread::started, + this, &QWinRTAbstractVideoRendererControl::syncAndRender, + Qt::DirectConnection); +} + +QWinRTAbstractVideoRendererControl::~QWinRTAbstractVideoRendererControl() +{ + shutdown(); +} + +QAbstractVideoSurface *QWinRTAbstractVideoRendererControl::surface() const +{ + Q_D(const QWinRTAbstractVideoRendererControl); + return d->surface; +} + +void QWinRTAbstractVideoRendererControl::setSurface(QAbstractVideoSurface *surface) +{ + Q_D(QWinRTAbstractVideoRendererControl); + d->surface = surface; +} + +void QWinRTAbstractVideoRendererControl::syncAndRender() +{ + Q_D(QWinRTAbstractVideoRendererControl); + + QThread *currentThread = QThread::currentThread(); + const QMetaMethod present = staticMetaObject.method(staticMetaObject.indexOfMethod("present()")); + forever { + if (currentThread->isInterruptionRequested()) + break; + + HRESULT hr; + if (d->dirtyState == TextureDirty) { + CD3D11_TEXTURE2D_DESC desc(DXGI_FORMAT_B8G8R8A8_UNORM, d->format.frameWidth(), d->format.frameHeight(), 1, 1); + desc.BindFlags |= D3D11_BIND_RENDER_TARGET; + desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED; + hr = g->device->CreateTexture2D(&desc, NULL, d->texture.ReleaseAndGetAddressOf()); + BREAK_IF_FAILED("Failed to get create video texture"); + ComPtr resource; + hr = d->texture.As(&resource); + BREAK_IF_FAILED("Failed to cast texture to resource"); + hr = resource->GetSharedHandle(&d->shareHandle); + BREAK_IF_FAILED("Failed to get texture share handle"); + d->dirtyState = SurfaceDirty; + } + + hr = g->output->WaitForVBlank(); + CONTINUE_IF_FAILED("Failed to wait for vertical blank"); + + if (!render(d->texture.Get())) + continue; + + // Queue to the control's thread for presentation + present.invoke(this, Qt::QueuedConnection); + currentThread->eventDispatcher()->processEvents(QEventLoop::AllEvents); + } + + // All done, exit render loop + currentThread->quit(); +} + +QSize QWinRTAbstractVideoRendererControl::size() const +{ + Q_D(const QWinRTAbstractVideoRendererControl); + return d->format.frameSize(); +} + +void QWinRTAbstractVideoRendererControl::setSize(const QSize &size) +{ + Q_D(QWinRTAbstractVideoRendererControl); + + if (d->format.frameSize() == size) + return; + + d->format.setFrameSize(size); + d->dirtyState = TextureDirty; +} + +void QWinRTAbstractVideoRendererControl::setActive(bool active) +{ + Q_D(QWinRTAbstractVideoRendererControl); + + if (d->active == active) + return; + + d->active = active; + if (d->active) { + if (!d->surface) + return; + + if (!d->surface->isActive()) + d->surface->start(d->format); + + d->renderThread.start(); + return; + } + + d->renderThread.requestInterruption(); +} + +void QWinRTAbstractVideoRendererControl::present() +{ + Q_D(QWinRTAbstractVideoRendererControl); + + if (d->dirtyState == SurfaceDirty) { + if (!QOpenGLContext::currentContext()) { + qWarning("A valid OpenGL context is required for binding the video texture."); + return; + } + + if (d->eglDisplay == EGL_NO_DISPLAY) + d->eglDisplay = eglGetCurrentDisplay(); + + if (d->eglDisplay == EGL_NO_DISPLAY) { + qWarning("Failed to get the current EGL display for video presentation: 0x%x", eglGetError()); + return; + } + + EGLint configAttributes[] = { + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_BIND_TO_TEXTURE_RGBA, EGL_TRUE, + EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, + EGL_NONE + }; + EGLint configCount; + if (!eglChooseConfig(d->eglDisplay, configAttributes, &d->eglConfig, 1, &configCount)) { + qWarning("Failed to create the texture EGL configuration for video presentation: 0x%x", eglGetError()); + return; + } + + if (d->eglSurface != EGL_NO_SURFACE) + eglDestroySurface(d->eglDisplay, d->eglSurface); + + EGLint bufferAttributes[] = { + EGL_WIDTH, d->format.frameWidth(), + EGL_HEIGHT, d->format.frameHeight(), + EGL_TEXTURE_FORMAT, EGL_TEXTURE_RGBA, + EGL_TEXTURE_TARGET, EGL_TEXTURE_2D, + EGL_NONE + }; + d->eglSurface = eglCreatePbufferFromClientBuffer(d->eglDisplay, + EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE, + d->shareHandle, d->eglConfig, bufferAttributes); + if (d->eglSurface == EGL_NO_SURFACE) { + qWarning("Failed to create the EGL configuration for video presentation: 0x%x", eglGetError()); + return; + } + + d->videoBuffer->setFormat(QOpenGLTexture::RGBAFormat); + d->videoBuffer->setSize(d->format.frameWidth(), d->format.frameHeight()); + if (!d->videoBuffer->isCreated()) + d->videoBuffer->create(); + + // bind the pbuffer surface to the texture + d->videoBuffer->bind(); + eglBindTexImage(d->eglDisplay, d->eglSurface, EGL_BACK_BUFFER); + static_cast(d->videoBuffer)->release(); + + d->dirtyState = NotDirty; + } + + // Present the frame + d->videoBuffer->addRef(); + QVideoFrame frame(d->videoBuffer, d->format.frameSize(), d->format.pixelFormat()); + d->surface->present(frame); +} diff --git a/src/plugins/winrt/qwinrtabstractvideorenderercontrol.h b/src/plugins/winrt/qwinrtabstractvideorenderercontrol.h new file mode 100644 index 000000000..86a7b15f9 --- /dev/null +++ b/src/plugins/winrt/qwinrtabstractvideorenderercontrol.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINRTABSTRACTVIDEORENDERERCONTROL_H +#define QWINRTABSTRACTVIDEORENDERERCONTROL_H + +#include + +struct ID3D11Device; +struct ID3D11Texture2D; + +QT_BEGIN_NAMESPACE + +class QWinRTAbstractVideoRendererControlPrivate; +class QWinRTAbstractVideoRendererControl : public QVideoRendererControl +{ + Q_OBJECT +public: + explicit QWinRTAbstractVideoRendererControl(const QSize &size, QObject *parent = 0); + ~QWinRTAbstractVideoRendererControl(); + + QAbstractVideoSurface *surface() const Q_DECL_OVERRIDE; + void setSurface(QAbstractVideoSurface *surface) Q_DECL_OVERRIDE; + + QSize size() const; + void setSize(const QSize &size); + + void setActive(bool active); + + virtual bool render(ID3D11Texture2D *texture) = 0; + + static ID3D11Device *d3dDevice(); + +protected: + void shutdown(); + +private slots: + void syncAndRender(); + +private: + Q_INVOKABLE void present(); + + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QWinRTAbstractVideoRendererControl) +}; + +QT_END_NAMESPACE + +#endif // QWINRTABSTRACTVIDEORENDERERCONTROL_H diff --git a/src/plugins/winrt/qwinrtmediaplayercontrol.cpp b/src/plugins/winrt/qwinrtmediaplayercontrol.cpp new file mode 100644 index 000000000..66c0bd568 --- /dev/null +++ b/src/plugins/winrt/qwinrtmediaplayercontrol.cpp @@ -0,0 +1,899 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwinrtmediaplayercontrol.h" +#include "qwinrtplayerrenderercontrol.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +using namespace Microsoft::WRL; + +QT_USE_NAMESPACE + +#define QT_WINRT_MEDIAPLAYER_STREAM_ID "__qtmultimedia_winrt_player_stream" + +class MediaEngineNotify; +class MediaEngineSources; +class MediaEngineByteStream; +class QWinRTMediaPlayerControlPrivate +{ +public: + QMediaPlayer::State state; + QMediaPlayer::MediaStatus mediaStatus; + qint64 duration; + qint64 position; + qreal playbackRate; + int volume; + bool muted; + int bufferStatus; + bool seekable; + bool hasVideo; + bool hasAudio; + + QMediaContent media; + QScopedPointer stream; + QWinRTPlayerRendererControl *videoRenderer; + + ComPtr notifier; + ComPtr sources; + ComPtr streamProvider; + ComPtr configuration; + ComPtr engine; + + quint32 resetToken; + ComPtr manager; + + // Automatically delete streams created by the player + static inline void cleanup(QIODevice *device) + { + if (device && device->property(QT_WINRT_MEDIAPLAYER_STREAM_ID).toBool()) + device->deleteLater(); + } + + // Allows for deferred cleanup of the engine, which tends to block on shutdown + static void cleanup(QWinRTMediaPlayerControlPrivate *d); + static void shutdown(QWinRTMediaPlayerControlPrivate *d); +}; + +class MediaEngineNotify : public RuntimeClass, IMFMediaEngineNotify> +{ +public: + MediaEngineNotify(QWinRTMediaPlayerControl *q_ptr, QWinRTMediaPlayerControlPrivate *d_ptr) + : q(q_ptr), d(d_ptr) + { + } + + HRESULT __stdcall EventNotify(DWORD event, DWORD_PTR param1, DWORD param2) + { + QMediaPlayer::State newState = d->state; + QMediaPlayer::MediaStatus newStatus = d->mediaStatus; + + switch (event) { + // Media change events + case MF_MEDIA_ENGINE_EVENT_LOADEDMETADATA: { + const bool hasAudio = d->engine->HasAudio(); + if (d->hasAudio != hasAudio) { + d->hasAudio = hasAudio; + emit q->audioAvailableChanged(d->hasAudio); + } + + const bool hasVideo = d->engine->HasVideo(); + if (d->hasVideo != hasVideo) { + d->hasVideo = hasVideo; + emit q->audioAvailableChanged(d->hasAudio); + } + + if (hasVideo) { + HRESULT hr; + DWORD width, height; + hr = d->engine->GetNativeVideoSize(&width, &height); + if (FAILED(hr)) + break; + d->videoRenderer->setSize(QSize(width, height)); + } + + newStatus = QMediaPlayer::LoadedMedia; + break; + } + case MF_MEDIA_ENGINE_EVENT_LOADSTART: + case MF_MEDIA_ENGINE_EVENT_PROGRESS: { + newStatus = QMediaPlayer::LoadingMedia; + break; + } + case MF_MEDIA_ENGINE_EVENT_CANPLAY: + d->bufferStatus = 100; // Fired when buffering is not used + newStatus = d->state == QMediaPlayer::StoppedState ? QMediaPlayer::LoadedMedia + : QMediaPlayer::BufferedMedia; + break; + case MF_MEDIA_ENGINE_EVENT_BUFFERINGSTARTED: + case MF_MEDIA_ENGINE_EVENT_BUFFERINGENDED: { + PROPVARIANT stat; + HRESULT hr = d->engine->GetStatistics(MF_MEDIA_ENGINE_STATISTIC_BUFFER_PROGRESS, &stat); + if (SUCCEEDED(hr)) { + d->bufferStatus = stat.lVal; + PropVariantClear(&stat); + } + newStatus = d->state == QMediaPlayer::StoppedState ? QMediaPlayer::LoadedMedia + : (d->bufferStatus == 100 ? QMediaPlayer::BufferedMedia : QMediaPlayer::BufferingMedia); + break; + } + //case MF_MEDIA_ENGINE_EVENT_SUSPEND: ??? + //case MF_MEDIA_ENGINE_EVENT_ABORT: ??? + case MF_MEDIA_ENGINE_EVENT_EMPTIED: { + newState = QMediaPlayer::StoppedState; + break; + } + // Transport controls + case MF_MEDIA_ENGINE_EVENT_PLAY: { + // If the media is already loaded, the playing event may not occur after stop + if (d->mediaStatus != QMediaPlayer::LoadedMedia) + break; + // fall through + } + case MF_MEDIA_ENGINE_EVENT_PLAYING: { + newState = QMediaPlayer::PlayingState; + newStatus = d->bufferStatus < 100 ? QMediaPlayer::BufferingMedia + : QMediaPlayer::BufferedMedia; + break; + } + case MF_MEDIA_ENGINE_EVENT_PAUSE: { + newState = QMediaPlayer::PausedState; + break; + } + case MF_MEDIA_ENGINE_EVENT_STALLED: { + newStatus = QMediaPlayer::StalledMedia; + break; + } + case MF_MEDIA_ENGINE_EVENT_WAITING: { + newStatus = QMediaPlayer::StalledMedia; + break; + } + case MF_MEDIA_ENGINE_EVENT_ENDED: { + newState = QMediaPlayer::StoppedState; + newStatus = QMediaPlayer::EndOfMedia; + break; + } + // Media attributes + case MF_MEDIA_ENGINE_EVENT_DURATIONCHANGE: { + double duration = d->engine->GetDuration() * 1000; + if (!qFuzzyCompare(d->duration, duration)) { + d->duration = duration; + emit q->durationChanged(d->duration); + } + break; + } + case MF_MEDIA_ENGINE_EVENT_TIMEUPDATE: { + double position = d->engine->GetCurrentTime() * 1000; + if (!qFuzzyCompare(d->position, position)) { + d->position = position; + emit q->positionChanged(d->position); + } + // Stopped state: paused at beginning + if (qFuzzyIsNull(position) && d->state == QMediaPlayer::PausedState) + newState = QMediaPlayer::StoppedState; + break; + } + case MF_MEDIA_ENGINE_EVENT_RATECHANGE: { + double playbackRate = d->engine->GetPlaybackRate(); + if (!qFuzzyCompare(d->playbackRate, playbackRate)) { + d->playbackRate = playbackRate; + emit q->playbackRateChanged(d->playbackRate); + } + break; + } + // Error handling + case MF_MEDIA_ENGINE_EVENT_ERROR: { + newState = QMediaPlayer::StoppedState; + newStatus = QMediaPlayer::InvalidMedia; + switch (param1) { + default: + case MF_MEDIA_ENGINE_ERR_NOERROR: + newStatus = QMediaPlayer::UnknownMediaStatus; + emit q->error(QMediaPlayer::ResourceError, qt_error_string(param2)); + break; + case MF_MEDIA_ENGINE_ERR_ABORTED: + if (d->mediaStatus == QMediaPlayer::StalledMedia || d->mediaStatus == QMediaPlayer::BufferingMedia) + d->mediaStatus = QMediaPlayer::LoadedMedia; + emit q->error(QMediaPlayer::ResourceError, QStringLiteral("The process of fetching the media resource was stopped at the user's request.")); + break; + case MF_MEDIA_ENGINE_ERR_NETWORK: + if (d->mediaStatus == QMediaPlayer::StalledMedia || d->mediaStatus == QMediaPlayer::BufferingMedia) + d->mediaStatus = QMediaPlayer::LoadedMedia; + emit q->error(QMediaPlayer::NetworkError, QStringLiteral("A network error occurred while fetching the media resource.")); + break; + case MF_MEDIA_ENGINE_ERR_DECODE: + emit q->error(QMediaPlayer::FormatError, QStringLiteral("An error occurred while decoding the media resource.")); + break; + case MF_MEDIA_ENGINE_ERR_SRC_NOT_SUPPORTED: + emit q->error(QMediaPlayer::FormatError, QStringLiteral("The media resource is not supported.")); + break; + case MF_MEDIA_ENGINE_ERR_ENCRYPTED: + emit q->error(QMediaPlayer::FormatError, QStringLiteral("An error occurred while encrypting the media resource.")); + break; + } + break; + } + default: + break; + } + + if (d->state != newState) { + d->state = newState; + emit q->stateChanged(d->state); + } + + if (d->videoRenderer) + d->videoRenderer->setActive(d->state == QMediaPlayer::PlayingState); + + if (d->mediaStatus != newStatus) { + d->mediaStatus = newStatus; + emit q->mediaStatusChanged(d->mediaStatus); + } + + return S_OK; + } + +private: + QWinRTMediaPlayerControl *q; + QWinRTMediaPlayerControlPrivate *d; +}; + +class MediaEngineSources : public RuntimeClass, IMFMediaEngineSrcElements> +{ +public: + MediaEngineSources(QWinRTMediaPlayerControlPrivate *d_ptr) + : d(d_ptr) + { + } + + DWORD __stdcall GetLength() + { + return d->media.resources().length(); + } + + HRESULT __stdcall GetURL(DWORD index, BSTR *url) + { + const QString resourceUrl = d->media.resources().value(index).url().toString(); + *url = SysAllocString((const OLECHAR *)resourceUrl.utf16()); + return S_OK; + } + + HRESULT __stdcall GetType(DWORD index, BSTR *type) + { + const QString resourceType = d->media.resources().value(index).mimeType(); + *type = SysAllocString((const OLECHAR *)resourceType.utf16()); + return S_OK; + } + + HRESULT __stdcall GetMedia(DWORD index, BSTR *media) + { + Q_UNUSED(index); + *media = NULL; + return S_OK; + } + + HRESULT __stdcall AddElement(BSTR url, BSTR type, BSTR media) + { + Q_UNUSED(url); + Q_UNUSED(type); + Q_UNUSED(media); + return E_NOTIMPL; + } + + HRESULT __stdcall RemoveAllElements() + { + return E_NOTIMPL; + } + +private: + QWinRTMediaPlayerControlPrivate *d; +}; + +class MediaEngineReadResult : public RuntimeClass, IUnknown> +{ +public: + MediaEngineReadResult(BYTE *bytes, ULONG maxLength) + : bytes(bytes), maxLength(maxLength) { } + + void read(QIODevice *device) + { + bytesRead = device->read(reinterpret_cast(bytes), maxLength); + } + + BYTE *bytes; + ULONG maxLength; + ULONG bytesRead; +}; + +class MediaEngineByteStream : public RuntimeClass, IMFByteStream> +{ +public: + MediaEngineByteStream(QWinRTMediaPlayerControl *q_ptr, QWinRTMediaPlayerControlPrivate *d_ptr) + : q(q_ptr), d(d_ptr) + { + } + + HRESULT __stdcall GetCapabilities(DWORD *capabilities) + { + *capabilities |= MFBYTESTREAM_IS_READABLE; + if (!d->stream->isSequential()) + *capabilities |= MFBYTESTREAM_IS_SEEKABLE; + return S_OK; + } + + HRESULT __stdcall GetLength(QWORD *length) + { + *length = QWORD(d->stream->size()); + return S_OK; + } + + HRESULT __stdcall SetLength(QWORD length) + { + Q_UNUSED(length); + return E_NOTIMPL; + } + + HRESULT __stdcall GetCurrentPosition(QWORD *position) + { + *position = QWORD(d->stream->pos()); + return S_OK; + } + + HRESULT __stdcall SetCurrentPosition(QWORD position) + { + qint64 pos(position); + if (pos >= d->stream->size()) + return E_INVALIDARG; + + const bool ok = d->stream->seek(pos); + return ok ? S_OK : S_FALSE; + } + + HRESULT __stdcall IsEndOfStream(BOOL *endOfStream) + { + *endOfStream = d->stream->atEnd() ? TRUE : FALSE; + return S_OK; + } + + HRESULT __stdcall Read(BYTE *bytes, ULONG maxlen, ULONG *bytesRead) + { + *bytesRead = d->stream->read(reinterpret_cast(bytes), maxlen); + return S_OK; + } + + HRESULT __stdcall BeginRead(BYTE *bytes, ULONG maxLength, IMFAsyncCallback *callback, IUnknown *state) + { + ComPtr readResult = Make(bytes, maxLength); + HRESULT hr; + hr = MFCreateAsyncResult(readResult.Get(), callback, state, &asyncResult); + RETURN_HR_IF_FAILED("Failed to create read callback result"); + QMetaObject::invokeMethod(q, "finishRead", Qt::QueuedConnection); + return S_OK; + } + + HRESULT __stdcall EndRead(IMFAsyncResult *result, ULONG *bytesRead) + { + HRESULT hr; + ComPtr readResult; + hr = result->GetObject(&readResult); + RETURN_HR_IF_FAILED("Failed to get read result"); + + *bytesRead = readResult->bytesRead; + return S_OK; + } + + HRESULT __stdcall Write(const BYTE *bytes, ULONG maxlen, ULONG *bytesWritten) + { + Q_UNUSED(bytes); + Q_UNUSED(maxlen); + Q_UNUSED(bytesWritten); + return E_NOTIMPL; + } + + HRESULT __stdcall BeginWrite(const BYTE *bytes, ULONG maxlen, IMFAsyncCallback *callback, IUnknown *state) + { + Q_UNUSED(bytes); + Q_UNUSED(maxlen); + Q_UNUSED(callback); + Q_UNUSED(state); + return E_NOTIMPL; + } + + HRESULT __stdcall EndWrite(IMFAsyncResult *result, ULONG *bytesWritten) + { + Q_UNUSED(result); + Q_UNUSED(bytesWritten); + return E_NOTIMPL; + } + + HRESULT __stdcall Seek(MFBYTESTREAM_SEEK_ORIGIN origin, LONGLONG offset, DWORD flags, QWORD *position) + { + Q_UNUSED(flags); + + qint64 pos = offset; + if (origin == msoCurrent) + pos += d->stream->pos(); + + const bool ok = d->stream->seek(pos); + *position = QWORD(d->stream->pos()); + + return ok ? S_OK : E_FAIL; + } + + HRESULT __stdcall Flush() + { + return E_NOTIMPL; + } + + HRESULT __stdcall Close() + { + if (asyncResult) + finishRead(); + + if (d->stream->property(QT_WINRT_MEDIAPLAYER_STREAM_ID).toBool()) + d->stream->close(); + + return S_OK; + } + + void finishRead() + { + if (!asyncResult) + return; + + HRESULT hr; + ComPtr readResult; + hr = asyncResult->GetObject(&readResult); + RETURN_VOID_IF_FAILED("Failed to get read result object"); + readResult->read(d->stream.data()); + hr = MFInvokeCallback(asyncResult.Get()); + RETURN_VOID_IF_FAILED("Failed to invoke read callback"); + asyncResult.Reset(); + } + +private: + QWinRTMediaPlayerControl *q; + QWinRTMediaPlayerControlPrivate *d; + + ComPtr asyncResult; +}; + +void QWinRTMediaPlayerControlPrivate::cleanup(QWinRTMediaPlayerControlPrivate *d) +{ + QtConcurrent::run(&QWinRTMediaPlayerControlPrivate::shutdown, d); +} + +void QWinRTMediaPlayerControlPrivate::shutdown(QWinRTMediaPlayerControlPrivate *d) +{ + d->engine->Shutdown(); + delete d; +} + +QWinRTMediaPlayerControl::QWinRTMediaPlayerControl(IMFMediaEngineClassFactory *factory, QObject *parent) + : QMediaPlayerControl(parent), d_ptr(new QWinRTMediaPlayerControlPrivate) +{ + Q_D(QWinRTMediaPlayerControl); + + d->state = QMediaPlayer::StoppedState; + d->mediaStatus = QMediaPlayer::NoMedia; + d->duration = 0; + d->position = 0; + d->playbackRate = 1.0; + d->volume = 100; + d->bufferStatus = 0; + d->muted = false; + d->seekable = false; + d->hasAudio = false; + d->hasVideo = false; + d->videoRenderer = Q_NULLPTR; + d->notifier = Make(this, d); + + HRESULT hr; + hr = MFCreateDXGIDeviceManager(&d->resetToken, &d->manager); + RETURN_VOID_IF_FAILED("Failed to create DXGI device manager"); + + hr = MFCreateAttributes(&d->configuration, 1); + Q_ASSERT_SUCCEEDED(hr); + hr = d->configuration->SetUnknown(MF_MEDIA_ENGINE_CALLBACK, d->notifier.Get()); + Q_ASSERT_SUCCEEDED(hr); + hr = d->configuration->SetUnknown(MF_MEDIA_ENGINE_DXGI_MANAGER, d->manager.Get()); + Q_ASSERT_SUCCEEDED(hr); + hr = d->configuration->SetUINT32(MF_MEDIA_ENGINE_VIDEO_OUTPUT_FORMAT, DXGI_FORMAT_B8G8R8A8_UNORM); + Q_ASSERT_SUCCEEDED(hr); + + ComPtr engine; + hr = factory->CreateInstance(0, d->configuration.Get(), &engine); + Q_ASSERT_SUCCEEDED(hr); + hr = engine.As(&d->engine); + Q_ASSERT_SUCCEEDED(hr); + + hr = d->engine->SetVolume(1.0); + Q_ASSERT_SUCCEEDED(hr); + + d->sources = Make(d); + hr = d->engine->SetSourceElements(d->sources.Get()); + Q_ASSERT_SUCCEEDED(hr); + + d->streamProvider = Make(this, d); +} + +QWinRTMediaPlayerControl::~QWinRTMediaPlayerControl() +{ +} + +QMediaPlayer::State QWinRTMediaPlayerControl::state() const +{ + Q_D(const QWinRTMediaPlayerControl); + return d->state; +} + +QMediaPlayer::MediaStatus QWinRTMediaPlayerControl::mediaStatus() const +{ + Q_D(const QWinRTMediaPlayerControl); + return d->mediaStatus; +} + +qint64 QWinRTMediaPlayerControl::duration() const +{ + Q_D(const QWinRTMediaPlayerControl); + return d->duration; +} + +qint64 QWinRTMediaPlayerControl::position() const +{ + Q_D(const QWinRTMediaPlayerControl); + return d->position; +} + +void QWinRTMediaPlayerControl::setPosition(qint64 position) +{ + Q_D(QWinRTMediaPlayerControl); + + if (d->position == position) + return; + + HRESULT hr; + hr = d->engine->SetCurrentTime(double(position)/1000); + RETURN_VOID_IF_FAILED("Failed to seek to new position"); + + d->position = position; + emit positionChanged(d->position); + + if (d->mediaStatus == QMediaPlayer::EndOfMedia) { + d->mediaStatus = QMediaPlayer::LoadedMedia; + emit mediaStatusChanged(d->mediaStatus); + } +} + +int QWinRTMediaPlayerControl::volume() const +{ + Q_D(const QWinRTMediaPlayerControl); + return d->volume; +} + +void QWinRTMediaPlayerControl::setVolume(int volume) +{ + Q_D(QWinRTMediaPlayerControl); + + if (d->volume == volume) + return; + + HRESULT hr; + hr = d->engine->SetVolume(double(volume)/100); + RETURN_VOID_IF_FAILED("Failed to set volume"); + + d->volume = volume; + emit volumeChanged(d->volume); +} + +bool QWinRTMediaPlayerControl::isMuted() const +{ + Q_D(const QWinRTMediaPlayerControl); + return d->muted; +} + +void QWinRTMediaPlayerControl::setMuted(bool muted) +{ + Q_D(QWinRTMediaPlayerControl); + + if (d->muted == muted) + return; + + HRESULT hr; + hr = d->engine->SetMuted(muted); + RETURN_VOID_IF_FAILED("Failed to mute volume"); + + d->muted = muted; + emit mutedChanged(d->muted); +} + +int QWinRTMediaPlayerControl::bufferStatus() const +{ + Q_D(const QWinRTMediaPlayerControl); + return d->bufferStatus; +} + +bool QWinRTMediaPlayerControl::isAudioAvailable() const +{ + Q_D(const QWinRTMediaPlayerControl); + return d->hasAudio; +} + +bool QWinRTMediaPlayerControl::isVideoAvailable() const +{ + Q_D(const QWinRTMediaPlayerControl); + return d->hasVideo; +} + +bool QWinRTMediaPlayerControl::isSeekable() const +{ + Q_D(const QWinRTMediaPlayerControl); + return d->seekable; +} + +QMediaTimeRange QWinRTMediaPlayerControl::availablePlaybackRanges() const +{ + Q_D(const QWinRTMediaPlayerControl); + return QMediaTimeRange(0, d->duration); +} + +qreal QWinRTMediaPlayerControl::playbackRate() const +{ + Q_D(const QWinRTMediaPlayerControl); + return d->playbackRate; +} + +void QWinRTMediaPlayerControl::setPlaybackRate(qreal rate) +{ + Q_D(QWinRTMediaPlayerControl); + + if (qFuzzyCompare(d->playbackRate, rate)) + return; + + HRESULT hr; + hr = d->engine->SetPlaybackRate(rate); + RETURN_VOID_IF_FAILED("Failed to set playback rate"); +} + +QMediaContent QWinRTMediaPlayerControl::media() const +{ + Q_D(const QWinRTMediaPlayerControl); + return d->media; +} + +const QIODevice *QWinRTMediaPlayerControl::mediaStream() const +{ + Q_D(const QWinRTMediaPlayerControl); + return d->stream.data(); +} + +void QWinRTMediaPlayerControl::setMedia(const QMediaContent &media, QIODevice *stream) +{ + Q_D(QWinRTMediaPlayerControl); + + if (d->media == media) + return; + + d->media = media; + d->stream.reset(stream); + if (d->hasAudio != false) { + d->hasAudio = false; + emit audioAvailableChanged(d->hasAudio); + } + if (d->hasVideo != false) { + d->hasVideo = false; + emit videoAvailableChanged(d->hasVideo); + } + if (d->seekable != false) { + d->seekable = false; + emit seekableChanged(d->seekable); + } + if (d->bufferStatus != 0) { + d->bufferStatus = 0; + emit bufferStatusChanged(d->bufferStatus); + } + if (d->position != 0) { + d->position = 0; + emit positionChanged(d->position); + } + if (d->duration != 0) { + d->duration = 0; + emit durationChanged(d->duration); + } + QMediaPlayer::MediaStatus mediaStatus = media.isNull() ? QMediaPlayer::NoMedia + : QMediaPlayer::LoadingMedia; + if (d->mediaStatus != mediaStatus) { + d->mediaStatus = mediaStatus; + emit mediaStatusChanged(d->mediaStatus); + } + emit mediaChanged(media); + + QString urlString; + if (!d->stream) { + // If we can read the file via Qt, use the byte stream approach + foreach (const QMediaResource &resource, media.resources()) { + const QUrl url = resource.url(); + if (url.isLocalFile()) { + urlString = url.toLocalFile(); + QScopedPointer file(new QFile(urlString)); + if (file->open(QFile::ReadOnly)) { + file->setProperty(QT_WINRT_MEDIAPLAYER_STREAM_ID, true); + d->stream.reset(file.take()); + break; + } + } + } + } + + HRESULT hr; + if (d->stream) { + hr = d->engine->SetSourceFromByteStream(d->streamProvider.Get(), + reinterpret_cast(urlString.data())); + if (FAILED(hr)) + emit error(QMediaPlayer::ResourceError, qt_error_string(hr)); + return; + } + + // Let Windows handle all other URLs + hr = d->engine->SetSource(Q_NULLPTR); // Resets the byte stream + Q_ASSERT_SUCCEEDED(hr); + hr = d->engine->Load(); + if (FAILED(hr)) + emit error(QMediaPlayer::ResourceError, qt_error_string(hr)); + + if (d->media.isNull() && d->stream.isNull()) + return; + + // Resume play/pause/stop + switch (d->state) { + case QMediaPlayer::StoppedState: + stop(); + break; + case QMediaPlayer::PlayingState: + play(); + break; + case QMediaPlayer::PausedState: + pause(); + break; + } +} + +void QWinRTMediaPlayerControl::play() +{ + Q_D(QWinRTMediaPlayerControl); + + if (d->state != QMediaPlayer::PlayingState) { + d->state = QMediaPlayer::PlayingState; + emit stateChanged(d->state); + } + + if (d->media.isNull() && d->stream.isNull()) + return; + + if (d->videoRenderer) + d->videoRenderer->ensureReady(); + + HRESULT hr = d->engine->Play(); + if (FAILED(hr)) { + emit error(QMediaPlayer::ResourceError, qt_error_string(hr)); + return; + } +} + +void QWinRTMediaPlayerControl::pause() +{ + Q_D(QWinRTMediaPlayerControl); + + if (d->state != QMediaPlayer::PausedState) { + d->state = QMediaPlayer::PausedState; + emit stateChanged(d->state); + } + + if (d->media.isNull() && d->stream.isNull()) + return; + + HRESULT hr; + hr = d->engine->Pause(); + if (FAILED(hr)) { + emit error(QMediaPlayer::ResourceError, qt_error_string(hr)); + return; + } +} + +void QWinRTMediaPlayerControl::stop() +{ + Q_D(QWinRTMediaPlayerControl); + + if (d->state != QMediaPlayer::StoppedState) { + d->state = QMediaPlayer::StoppedState; + emit stateChanged(d->state); + } + + if (d->mediaStatus == QMediaPlayer::BufferedMedia + || d->mediaStatus == QMediaPlayer::BufferingMedia) { + d->mediaStatus = QMediaPlayer::LoadedMedia; + emit mediaStatusChanged(d->mediaStatus); + } + + if (d->media.isNull() && d->stream.isNull()) + return; + + setPosition(0); + + HRESULT hr; + hr = d->engine->Pause(); + if (FAILED(hr)) { + emit error(QMediaPlayer::ResourceError, qt_error_string(hr)); + return; + } +} + +QVideoRendererControl *QWinRTMediaPlayerControl::videoRendererControl() +{ + Q_D(QWinRTMediaPlayerControl); + + if (!d->videoRenderer) { + d->videoRenderer = new QWinRTPlayerRendererControl(d->engine.Get(), d->manager.Get(), + d->resetToken, this); + } + + return d->videoRenderer; +} + +void QWinRTMediaPlayerControl::finishRead() +{ + Q_D(QWinRTMediaPlayerControl); + d->streamProvider->finishRead(); +} diff --git a/src/plugins/winrt/qwinrtmediaplayercontrol.h b/src/plugins/winrt/qwinrtmediaplayercontrol.h new file mode 100644 index 000000000..b615b62d9 --- /dev/null +++ b/src/plugins/winrt/qwinrtmediaplayercontrol.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINRTMEDIAPLAYERCONTROL_H +#define QWINRTMEDIAPLAYERCONTROL_H + +#include + +struct IMFMediaEngineClassFactory; + +QT_USE_NAMESPACE + +class QVideoRendererControl; + +class QWinRTMediaPlayerControlPrivate; +class QWinRTMediaPlayerControl : public QMediaPlayerControl +{ + Q_OBJECT +public: + QWinRTMediaPlayerControl(IMFMediaEngineClassFactory *factory, QObject *parent = 0); + ~QWinRTMediaPlayerControl(); + + QMediaPlayer::State state() const Q_DECL_OVERRIDE; + QMediaPlayer::MediaStatus mediaStatus() const Q_DECL_OVERRIDE; + + qint64 duration() const Q_DECL_OVERRIDE; + + qint64 position() const Q_DECL_OVERRIDE; + void setPosition(qint64 position) Q_DECL_OVERRIDE; + + int volume() const Q_DECL_OVERRIDE; + void setVolume(int volume) Q_DECL_OVERRIDE; + + bool isMuted() const Q_DECL_OVERRIDE; + void setMuted(bool muted) Q_DECL_OVERRIDE; + + int bufferStatus() const Q_DECL_OVERRIDE; + + bool isAudioAvailable() const Q_DECL_OVERRIDE; + bool isVideoAvailable() const Q_DECL_OVERRIDE; + + bool isSeekable() const Q_DECL_OVERRIDE; + + QMediaTimeRange availablePlaybackRanges() const Q_DECL_OVERRIDE; + + qreal playbackRate() const Q_DECL_OVERRIDE; + void setPlaybackRate(qreal rate) Q_DECL_OVERRIDE; + + QMediaContent media() const Q_DECL_OVERRIDE; + const QIODevice *mediaStream() const Q_DECL_OVERRIDE; + void setMedia(const QMediaContent &media, QIODevice *stream) Q_DECL_OVERRIDE; + + void play() Q_DECL_OVERRIDE; + void pause() Q_DECL_OVERRIDE; + void stop() Q_DECL_OVERRIDE; + + QVideoRendererControl *videoRendererControl(); + +private: + Q_INVOKABLE void finishRead(); + + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QWinRTMediaPlayerControl) +}; + +#endif // QWINRTMEDIAPLAYERCONTROL_H diff --git a/src/plugins/winrt/qwinrtmediaplayerservice.cpp b/src/plugins/winrt/qwinrtmediaplayerservice.cpp new file mode 100644 index 000000000..cc1473887 --- /dev/null +++ b/src/plugins/winrt/qwinrtmediaplayerservice.cpp @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwinrtmediaplayerservice.h" +#include "qwinrtmediaplayercontrol.h" + +#include +#include +#include + +#include +#include +#include + +using namespace Microsoft::WRL; + +QT_USE_NAMESPACE + +class QWinRTMediaPlayerServicePrivate +{ +public: + QPointer player; + + ComPtr factory; +}; + +QWinRTMediaPlayerService::QWinRTMediaPlayerService(QObject *parent) + : QMediaService(parent), d_ptr(new QWinRTMediaPlayerServicePrivate) +{ + Q_D(QWinRTMediaPlayerService); + + d->player = Q_NULLPTR; + + HRESULT hr = MFStartup(MF_VERSION); + Q_ASSERT(SUCCEEDED(hr)); + + MULTI_QI results = { &IID_IUnknown, NULL, 0 }; + hr = CoCreateInstanceFromApp(CLSID_MFMediaEngineClassFactory, NULL, + CLSCTX_INPROC_SERVER, NULL, 1, &results); + Q_ASSERT(SUCCEEDED(hr)); + + hr = results.pItf->QueryInterface(d->factory.GetAddressOf()); + Q_ASSERT(SUCCEEDED(hr)); +} + +QWinRTMediaPlayerService::~QWinRTMediaPlayerService() +{ + MFShutdown(); +} + +QMediaControl *QWinRTMediaPlayerService::requestControl(const char *name) +{ + Q_D(QWinRTMediaPlayerService); + if (qstrcmp(name, QMediaPlayerControl_iid) == 0) { + if (!d->player) + d->player = new QWinRTMediaPlayerControl(d->factory.Get(), this); + return d->player; + } + if (qstrcmp(name, QVideoRendererControl_iid) == 0) { + if (!d->player) + return Q_NULLPTR; + return d->player->videoRendererControl(); + } + + return Q_NULLPTR; +} + +void QWinRTMediaPlayerService::releaseControl(QMediaControl *control) +{ + Q_D(QWinRTMediaPlayerService); + if (control == d->player) + d->player->deleteLater(); +} diff --git a/src/plugins/winrt/qwinrtmediaplayerservice.h b/src/plugins/winrt/qwinrtmediaplayerservice.h new file mode 100644 index 000000000..9a2498365 --- /dev/null +++ b/src/plugins/winrt/qwinrtmediaplayerservice.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINRTMEDIAPLAYERSERVICE_H +#define QWINRTMEDIAPLAYERSERVICE_H + +#include + +QT_BEGIN_NAMESPACE + +class QWinRTMediaPlayerServicePrivate; +class QWinRTMediaPlayerService : public QMediaService +{ + Q_OBJECT +public: + QWinRTMediaPlayerService(QObject *parent); + ~QWinRTMediaPlayerService(); + + QMediaControl *requestControl(const char *name); + void releaseControl(QMediaControl *control); + +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QWinRTMediaPlayerService) +}; + +QT_END_NAMESPACE + +#endif // QWINRTMEDIAPLAYERSERVICE_H diff --git a/src/plugins/winrt/qwinrtplayerrenderercontrol.cpp b/src/plugins/winrt/qwinrtplayerrenderercontrol.cpp new file mode 100644 index 000000000..273a650df --- /dev/null +++ b/src/plugins/winrt/qwinrtplayerrenderercontrol.cpp @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwinrtplayerrenderercontrol.h" + +#include +#include +#include +#include +#include +#include +#include + +#define EGL_EGLEXT_PROTOTYPES +#include +#include + +#include +#include +#include +#include +using namespace Microsoft::WRL; + +QT_USE_NAMESPACE + +template +class D3DDeviceLocker +{ +public: + D3DDeviceLocker(IMFDXGIDeviceManager *manager, HANDLE deviceHandle, T **device) + : m_manager(manager), m_deviceHandle(deviceHandle) + { + HRESULT hr = m_manager->LockDevice(m_deviceHandle, IID_PPV_ARGS(device), TRUE); + if (FAILED(hr)) + qErrnoWarning(hr, "Failed to lock the D3D device"); + } + + ~D3DDeviceLocker() + { + HRESULT hr = m_manager->UnlockDevice(m_deviceHandle, FALSE); + if (FAILED(hr)) + qErrnoWarning(hr, "Failed to unlock the D3D device"); + } + +private: + IMFDXGIDeviceManager *m_manager; + HANDLE m_deviceHandle; +}; + +class QWinRTPlayerRendererControlPrivate +{ +public: + ComPtr engine; + ComPtr manager; + quint32 resetToken; + HANDLE deviceHandle; +}; + +QWinRTPlayerRendererControl::QWinRTPlayerRendererControl(IMFMediaEngineEx *engine, IMFDXGIDeviceManager *manager, quint32 resetToken, QObject *parent) + : QWinRTAbstractVideoRendererControl(QSize(), parent), d_ptr(new QWinRTPlayerRendererControlPrivate) +{ + Q_D(QWinRTPlayerRendererControl); + + d->engine = engine; + d->manager = manager; + d->resetToken = resetToken; + d->deviceHandle = 0; +} + +QWinRTPlayerRendererControl::~QWinRTPlayerRendererControl() +{ + shutdown(); +} + +bool QWinRTPlayerRendererControl::ensureReady() +{ + Q_D(QWinRTPlayerRendererControl); + + HRESULT hr; + hr = d->manager->TestDevice(d->deviceHandle); + if (hr == MF_E_DXGI_NEW_VIDEO_DEVICE || FAILED(hr)) { + d->manager->CloseDeviceHandle(d->deviceHandle); + + hr = d->manager->ResetDevice(d3dDevice(), d->resetToken); + RETURN_FALSE_IF_FAILED("Failed to reset the DXGI manager device"); + + hr = d->manager->OpenDeviceHandle(&d->deviceHandle); + RETURN_FALSE_IF_FAILED("Failed to open device handle"); + } + + return SUCCEEDED(hr); +} + +bool QWinRTPlayerRendererControl::render(ID3D11Texture2D *texture) +{ + Q_D(QWinRTPlayerRendererControl); + + if (!ensureReady()) + return false; + + HRESULT hr; + LONGLONG ts; + hr = d->engine->OnVideoStreamTick(&ts); + RETURN_FALSE_IF_FAILED("Failed to obtain video stream tick"); + if (hr == S_FALSE) // Frame not available, nothing to do + return false; + + ComPtr device; + D3DDeviceLocker locker(d->manager.Get(), d->deviceHandle, device.GetAddressOf()); + + MFVideoNormalizedRect sourceRect = { 0, 0, 1, 1 }; + const QSize frameSize = size(); + RECT destRect = { 0, 0, frameSize.width(), frameSize.height() }; + MFARGB borderColor = { 255, 255, 255, 255 }; + hr = d->engine->TransferVideoFrame(texture, &sourceRect, &destRect, &borderColor); + RETURN_FALSE_IF_FAILED("Failed to transfer video frame to DXGI surface"); + return true; +} diff --git a/src/plugins/winrt/qwinrtplayerrenderercontrol.h b/src/plugins/winrt/qwinrtplayerrenderercontrol.h new file mode 100644 index 000000000..2d973cbfc --- /dev/null +++ b/src/plugins/winrt/qwinrtplayerrenderercontrol.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINRTPLAYERRENDERERCONTROL_H +#define QWINRTPLAYERRENDERERCONTROL_H + +#include "qwinrtabstractvideorenderercontrol.h" +#include + +struct IMFMediaEngineEx; +struct IMFDXGIDeviceManager; + +QT_BEGIN_NAMESPACE + +class QWinRTPlayerRendererControlPrivate; +class QWinRTPlayerRendererControl : public QWinRTAbstractVideoRendererControl +{ + Q_OBJECT +public: + explicit QWinRTPlayerRendererControl(IMFMediaEngineEx *engine, IMFDXGIDeviceManager *manager, quint32 resetToken, QObject *parent); + ~QWinRTPlayerRendererControl(); + + bool ensureReady(); + + bool render(ID3D11Texture2D *texture) Q_DECL_OVERRIDE; + +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QWinRTPlayerRendererControl) +}; + +QT_END_NAMESPACE + +#endif // QWINRTPLAYERRENDERERCONTROL_H diff --git a/src/plugins/winrt/qwinrtserviceplugin.cpp b/src/plugins/winrt/qwinrtserviceplugin.cpp index 143b93547..5d49b44a4 100644 --- a/src/plugins/winrt/qwinrtserviceplugin.cpp +++ b/src/plugins/winrt/qwinrtserviceplugin.cpp @@ -43,9 +43,15 @@ #include #include "qwinrtserviceplugin.h" +#include "qwinrtmediaplayerservice.h" + +QT_USE_NAMESPACE QMediaService *QWinRTServicePlugin::create(QString const &key) { + if (key == QLatin1String(Q_MEDIASERVICE_MEDIAPLAYER)) + return new QWinRTMediaPlayerService(this); + return Q_NULLPTR; } @@ -57,5 +63,8 @@ void QWinRTServicePlugin::release(QMediaService *service) QMediaServiceProviderHint::Features QWinRTServicePlugin::supportedFeatures( const QByteArray &service) const { + if (service == Q_MEDIASERVICE_MEDIAPLAYER) + return QMediaServiceProviderHint::StreamPlayback | QMediaServiceProviderHint::VideoSurface; + return QMediaServiceProviderHint::Features(); } diff --git a/src/plugins/winrt/winrt.json b/src/plugins/winrt/winrt.json index f4a1ceb47..b85cfeb12 100644 --- a/src/plugins/winrt/winrt.json +++ b/src/plugins/winrt/winrt.json @@ -1,4 +1,4 @@ { "Keys": ["winrt"], - "Services": [] + "Services": ["org.qt-project.qt.mediaplayer"] } diff --git a/src/plugins/winrt/winrt.pro b/src/plugins/winrt/winrt.pro index 82f97e489..0ea90d22e 100644 --- a/src/plugins/winrt/winrt.pro +++ b/src/plugins/winrt/winrt.pro @@ -8,9 +8,17 @@ load(qt_plugin) LIBS += -lmfplat -lmfuuid -loleaut32 -ld3d11 HEADERS += \ + qwinrtabstractvideorenderercontrol.h \ + qwinrtmediaplayercontrol.h \ + qwinrtmediaplayerservice.h \ + qwinrtplayerrenderercontrol.h \ qwinrtserviceplugin.h SOURCES += \ + qwinrtabstractvideorenderercontrol.cpp \ + qwinrtmediaplayercontrol.cpp \ + qwinrtmediaplayerservice.cpp \ + qwinrtplayerrenderercontrol.cpp \ qwinrtserviceplugin.cpp OTHER_FILES += \ -- cgit v1.2.3