summaryrefslogtreecommitdiffstats
path: root/src/plugins/winrt/qwinrtabstractvideorenderercontrol.cpp
diff options
context:
space:
mode:
authorAndrew Knight <andrew.knight@digia.com>2014-08-21 10:55:10 +0300
committerMaurice Kalinowski <maurice.kalinowski@digia.com>2014-08-25 16:05:24 +0200
commit778b233bcdbc2942a44efd468f6bfb4c0ce5d5cf (patch)
tree69e00b0db784542badbb2a6d3200b2356f1c3d3c /src/plugins/winrt/qwinrtabstractvideorenderercontrol.cpp
parent5d04ed2a0a02e483196bd88ca059b7cfcb52a6ac (diff)
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 <yoann.lopes@digia.com>
Diffstat (limited to 'src/plugins/winrt/qwinrtabstractvideorenderercontrol.cpp')
-rw-r--r--src/plugins/winrt/qwinrtabstractvideorenderercontrol.cpp403
1 files changed, 403 insertions, 0 deletions
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 <QtCore/qfunctions_winrt.h>
+#include <QtCore/QGlobalStatic>
+#include <QtCore/QMetaMethod>
+#include <QtGui/QOpenGLContext>
+#include <QtGui/QOpenGLTexture>
+#include <QtMultimedia/QAbstractVideoBuffer>
+#include <QtMultimedia/QAbstractVideoSurface>
+#include <QtMultimedia/QVideoSurfaceFormat>
+
+#define EGL_EGLEXT_PROTOTYPES
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#include <d3d11.h>
+#include <mfapi.h>
+#include <wrl.h>
+
+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<ID3D10Multithread> multithread;
+ hr = device.As(&multithread);
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = multithread->SetMultithreadProtected(true);
+ Q_ASSERT_SUCCEEDED(hr);
+
+ ComPtr<IDXGIDevice> dxgiDevice;
+ hr = device.As(&dxgiDevice);
+ Q_ASSERT_SUCCEEDED(hr);
+ ComPtr<IDXGIAdapter> adapter;
+ hr = dxgiDevice->GetAdapter(&adapter);
+ Q_ASSERT_SUCCEEDED(hr);
+ hr = adapter->EnumOutputs(0, &output);
+ Q_ASSERT_SUCCEEDED(hr);
+ }
+
+ ComPtr<ID3D11Device> device;
+ ComPtr<ID3D11DeviceContext> context;
+ D3D_FEATURE_LEVEL featureLevel;
+ ComPtr<IDXGIOutput> 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<ID3D11Texture2D> 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<IDXGIResource> 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<QOpenGLTexture *>(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);
+}