diff options
Diffstat (limited to 'src/multimediakit/qgraphicsvideoitem_maemo5.cpp')
-rw-r--r-- | src/multimediakit/qgraphicsvideoitem_maemo5.cpp | 647 |
1 files changed, 647 insertions, 0 deletions
diff --git a/src/multimediakit/qgraphicsvideoitem_maemo5.cpp b/src/multimediakit/qgraphicsvideoitem_maemo5.cpp new file mode 100644 index 000000000..8551b313d --- /dev/null +++ b/src/multimediakit/qgraphicsvideoitem_maemo5.cpp @@ -0,0 +1,647 @@ +/**************************************************************************** +** +** 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$ +** GNU Lesser General Public License Usage +** 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. +** +** 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qpointer.h> +#include <QtCore/qdatetime.h> +#include <QtCore/qbasictimer.h> +#include <QtCore/qcoreevent.h> +#include <QtGui/qgraphicsscene.h> +#include <QtGui/qgraphicsview.h> +#include <QtGui/qscrollbar.h> +#include <QtGui/qx11info_x11.h> + +#include "qgraphicsvideoitem.h" + +#include <qmediaobject.h> +#include <qmediaservice.h> +#include <qpaintervideosurface_p.h> +#include <qvideorenderercontrol.h> + +#include <qvideosurfaceformat.h> + +#include "qxvideosurface_maemo5_p.h" + + +QT_BEGIN_NAMESPACE + +//#define DEBUG_GFX_VIDEO_ITEM + +//update overlay geometry slightly later, +//to ensure color key is alredy replaced with static frame +#define GEOMETRY_UPDATE_DELAY 20 +//this is necessary to prevent flickering, see maemo bug 8798 +//on geometry changes, the color key is replaced with static image frame +//until the overlay is re-initialized +#define SOFTWARE_RENDERING_DURATION 150 + +#ifdef __ARM_NEON__ + +/* +* ARM NEON optimized implementation of UYVY -> RGB16 convertor +*/ +static void uyvy422_to_rgb16_line_neon (uint8_t * dst, const uint8_t * src, int n) +{ + /* and this is the NEON code itself */ + static __attribute__ ((aligned (16))) uint16_t acc_r[8] = { + 22840, 22840, 22840, 22840, 22840, 22840, 22840, 22840, + }; + static __attribute__ ((aligned (16))) uint16_t acc_g[8] = { + 17312, 17312, 17312, 17312, 17312, 17312, 17312, 17312, + }; + static __attribute__ ((aligned (16))) uint16_t acc_b[8] = { + 28832, 28832, 28832, 28832, 28832, 28832, 28832, 28832, + }; + /* + * Registers: + * q0, q1 : d0, d1, d2, d3 - are used for initial loading of YUV data + * q2 : d4, d5 - are used for storing converted RGB data + * q3 : d6, d7 - are used for temporary storage + * + * q6 : d12, d13 - are used for converting to RGB16 + * q7 : d14, d15 - are used for storing RGB16 data + * q4-q5 - reserved + * + * q8, q9 : d16, d17, d18, d19 - are used for expanded Y data + * q10 : d20, d21 + * q11 : d22, d23 + * q12 : d24, d25 + * q13 : d26, d27 + * q13, q14, q15 - various constants (#16, #149, #204, #50, #104, #154) + */ + asm volatile (".macro convert_macroblock size\n" + /* load up to 16 source pixels in UYVY format */ + ".if \\size == 16\n" + "pld [%[src], #128]\n" + "vld1.32 {d0, d1, d2, d3}, [%[src]]!\n" + ".elseif \\size == 8\n" + "vld1.32 {d0, d1}, [%[src]]!\n" + ".elseif \\size == 4\n" + "vld1.32 {d0}, [%[src]]!\n" + ".elseif \\size == 2\n" + "vld1.32 {d0[0]}, [%[src]]!\n" + ".else\n" ".error \"unsupported macroblock size\"\n" ".endif\n" + /* convert from 'packed' to 'planar' representation */ + "vuzp.8 d0, d1\n" /* d1 - separated Y data (first 8 bytes) */ + "vuzp.8 d2, d3\n" /* d3 - separated Y data (next 8 bytes) */ + "vuzp.8 d0, d2\n" /* d0 - separated U data, d2 - separated V data */ + /* split even and odd Y color components */ + "vuzp.8 d1, d3\n" /* d1 - evenY, d3 - oddY */ + /* clip upper and lower boundaries */ + "vqadd.u8 q0, q0, q4\n" + "vqadd.u8 q1, q1, q4\n" + "vqsub.u8 q0, q0, q5\n" + "vqsub.u8 q1, q1, q5\n" + "vshr.u8 d4, d2, #1\n" /* d4 = V >> 1 */ + "vmull.u8 q8, d1, d27\n" /* q8 = evenY * 149 */ + "vmull.u8 q9, d3, d27\n" /* q9 = oddY * 149 */ + "vld1.16 {d20, d21}, [%[acc_r], :128]\n" /* q10 - initialize accumulator for red */ + "vsubw.u8 q10, q10, d4\n" /* red acc -= (V >> 1) */ + "vmlsl.u8 q10, d2, d28\n" /* red acc -= V * 204 */ + "vld1.16 {d22, d23}, [%[acc_g], :128]\n" /* q11 - initialize accumulator for green */ + "vmlsl.u8 q11, d2, d30\n" /* green acc -= V * 104 */ + "vmlsl.u8 q11, d0, d29\n" /* green acc -= U * 50 */ + "vld1.16 {d24, d25}, [%[acc_b], :128]\n" /* q12 - initialize accumulator for blue */ + "vmlsl.u8 q12, d0, d30\n" /* blue acc -= U * 104 */ + "vmlsl.u8 q12, d0, d31\n" /* blue acc -= U * 154 */ + "vhsub.s16 q3, q8, q10\n" /* calculate even red components */ + "vhsub.s16 q10, q9, q10\n" /* calculate odd red components */ + "vqshrun.s16 d0, q3, #6\n" /* right shift, narrow and saturate even red components */ + "vqshrun.s16 d3, q10, #6\n" /* right shift, narrow and saturate odd red components */ + "vhadd.s16 q3, q8, q11\n" /* calculate even green components */ + "vhadd.s16 q11, q9, q11\n" /* calculate odd green components */ + "vqshrun.s16 d1, q3, #6\n" /* right shift, narrow and saturate even green components */ + "vqshrun.s16 d4, q11, #6\n" /* right shift, narrow and saturate odd green components */ + "vhsub.s16 q3, q8, q12\n" /* calculate even blue components */ + "vhsub.s16 q12, q9, q12\n" /* calculate odd blue components */ + "vqshrun.s16 d2, q3, #6\n" /* right shift, narrow and saturate even blue components */ + "vqshrun.s16 d5, q12, #6\n" /* right shift, narrow and saturate odd blue components */ + "vzip.8 d0, d3\n" /* join even and odd red components */ + "vzip.8 d1, d4\n" /* join even and odd green components */ + "vzip.8 d2, d5\n" /* join even and odd blue components */ + "vshll.u8 q7, d0, #8\n" //red + "vshll.u8 q6, d1, #8\n" //greed + "vsri.u16 q7, q6, #5\n" + "vshll.u8 q6, d2, #8\n" //blue + "vsri.u16 q7, q6, #11\n" //now there is rgb16 in q7 + ".if \\size == 16\n" + "vst1.16 {d14, d15}, [%[dst]]!\n" + //"vst3.8 {d0, d1, d2}, [%[dst]]!\n" + "vshll.u8 q7, d3, #8\n" //red + "vshll.u8 q6, d4, #8\n" //greed + "vsri.u16 q7, q6, #5\n" + "vshll.u8 q6, d5, #8\n" //blue + "vsri.u16 q7, q6, #11\n" //now there is rgb16 in q7 + //"vst3.8 {d3, d4, d5}, [%[dst]]!\n" + "vst1.16 {d14, d15}, [%[dst]]!\n" + ".elseif \\size == 8\n" + "vst1.16 {d14, d15}, [%[dst]]!\n" + //"vst3.8 {d0, d1, d2}, [%[dst]]!\n" + ".elseif \\size == 4\n" + "vst1.8 {d14}, [%[dst]]!\n" + ".elseif \\size == 2\n" + "vst1.8 {d14[0]}, [%[dst]]!\n" + "vst1.8 {d14[1]}, [%[dst]]!\n" + ".else\n" + ".error \"unsupported macroblock size\"\n" + ".endif\n" + ".endm\n" + "vmov.u8 d8, #15\n" /* add this to U/V to saturate upper boundary */ + "vmov.u8 d9, #20\n" /* add this to Y to saturate upper boundary */ + "vmov.u8 d10, #31\n" /* sub this from U/V to saturate lower boundary */ + "vmov.u8 d11, #36\n" /* sub this from Y to saturate lower boundary */ + "vmov.u8 d26, #16\n" + "vmov.u8 d27, #149\n" + "vmov.u8 d28, #204\n" + "vmov.u8 d29, #50\n" + "vmov.u8 d30, #104\n" + "vmov.u8 d31, #154\n" + "subs %[n], %[n], #16\n" + "blt 2f\n" + "1:\n" + "convert_macroblock 16\n" + "subs %[n], %[n], #16\n" + "bge 1b\n" + "2:\n" + "tst %[n], #8\n" + "beq 3f\n" + "convert_macroblock 8\n" + "3:\n" + "tst %[n], #4\n" + "beq 4f\n" + "convert_macroblock 4\n" + "4:\n" + "tst %[n], #2\n" + "beq 5f\n" + "convert_macroblock 2\n" + "5:\n" + ".purgem convert_macroblock\n":[src] "+&r" (src),[dst] "+&r" (dst), + [n] "+&r" (n) + :[acc_r] "r" (&acc_r[0]),[acc_g] "r" (&acc_g[0]),[acc_b] "r" (&acc_b[0]) + :"cc", "memory", "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9", "d10", "d11", "d12", "d13", "d14", "d15", + "d16", "d17", "d18", "d19", "d20", "d21", "d22", "d23", + "d24", "d25", "d26", "d27", "d28", "d29", "d30", "d31"); +} + +#endif + +class QGraphicsVideoItemPrivate +{ +public: + QGraphicsVideoItemPrivate() + : q_ptr(0) + , surface(0) + , mediaObject(0) + , service(0) + , rendererControl(0) + , savedViewportUpdateMode(QGraphicsView::FullViewportUpdate) + , aspectRatioMode(Qt::KeepAspectRatio) + , rect(0.0, 0.0, 320, 240) + , softwareRenderingEnabled(false) + { + } + + QGraphicsVideoItem *q_ptr; + + QXVideoSurface *surface; + QMediaObject *mediaObject; + QMediaService *service; + QVideoRendererControl *rendererControl; + QPointer<QGraphicsView> currentView; + QGraphicsView::ViewportUpdateMode savedViewportUpdateMode; + + Qt::AspectRatioMode aspectRatioMode; + QRectF rect; + QRectF boundingRect; + QRectF sourceRect; + QSizeF nativeSize; + + QPixmap lastFrame; + QBasicTimer softwareRenderingTimer; + QBasicTimer geometryUpdateTimer; + bool softwareRenderingEnabled; + QRect overlayRect; + + void clearService(); + void updateRects(); + void updateLastFrame(); + + void _q_present(); + void _q_updateNativeSize(); + void _q_serviceDestroyed(); + void _q_mediaObjectDestroyed(); +}; + +void QGraphicsVideoItemPrivate::clearService() +{ + if (rendererControl) { + surface->stop(); + rendererControl->setSurface(0); + service->releaseControl(rendererControl); + rendererControl = 0; + } + + if (service) { + QObject::disconnect(service, SIGNAL(destroyed()), q_ptr, SLOT(_q_serviceDestroyed())); + service = 0; + } +} + +void QGraphicsVideoItemPrivate::updateRects() +{ + q_ptr->prepareGeometryChange(); + + if (nativeSize.isEmpty()) { + boundingRect = QRectF(); + } else if (aspectRatioMode == Qt::IgnoreAspectRatio) { + boundingRect = rect; + sourceRect = QRectF(0, 0, 1, 1); + } else if (aspectRatioMode == Qt::KeepAspectRatio) { + QSizeF size = nativeSize; + size.scale(rect.size(), Qt::KeepAspectRatio); + + boundingRect = QRectF(0, 0, size.width(), size.height()); + boundingRect.moveCenter(rect.center()); + + sourceRect = QRectF(0, 0, 1, 1); + } else if (aspectRatioMode == Qt::KeepAspectRatioByExpanding) { + boundingRect = rect; + + QSizeF size = rect.size(); + size.scale(nativeSize, Qt::KeepAspectRatio); + + sourceRect = QRectF( + 0, 0, size.width() / nativeSize.width(), size.height() / nativeSize.height()); + sourceRect.moveCenter(QPointF(0.5, 0.5)); + } +} + +void QGraphicsVideoItemPrivate::updateLastFrame() +{ + lastFrame = QPixmap(); + + if (!softwareRenderingEnabled) + return; + + QVideoFrame lastVideoFrame = surface->lastFrame(); + + if (!lastVideoFrame.isValid()) + return; + + if (lastVideoFrame.map(QAbstractVideoBuffer::ReadOnly)) { + +#ifdef __ARM_NEON__ + if (lastVideoFrame.pixelFormat() == QVideoFrame::Format_UYVY) { + QImage lastImage(lastVideoFrame.size(), QImage::Format_RGB16); + + const uchar *src = lastVideoFrame.bits(); + uchar *dst = lastImage.bits(); + const int srcLineStep = lastVideoFrame.bytesPerLine(); + const int dstLineStep = lastImage.bytesPerLine(); + const int h = lastVideoFrame.height(); + const int w = lastVideoFrame.width(); + + for (int y=0; y<h; y++) { + uyvy422_to_rgb16_line_neon(dst, src, w); + src += srcLineStep; + dst += dstLineStep; + } + lastFrame = QPixmap::fromImage( + lastImage.scaled(boundingRect.size().toSize(), Qt::IgnoreAspectRatio, Qt::FastTransformation)); + } else +#endif + { + QImage::Format imgFormat = QVideoFrame::imageFormatFromPixelFormat(lastVideoFrame.pixelFormat()); + + if (imgFormat != QImage::Format_Invalid) { + QImage lastImage(lastVideoFrame.bits(), + lastVideoFrame.width(), + lastVideoFrame.height(), + lastVideoFrame.bytesPerLine(), + imgFormat); + + lastFrame = QPixmap::fromImage( + lastImage.scaled(boundingRect.size().toSize(), Qt::IgnoreAspectRatio, Qt::FastTransformation)); + } + } + + lastVideoFrame.unmap(); + } + +} + +void QGraphicsVideoItemPrivate::_q_present() +{ + q_ptr->update(boundingRect); +} + +void QGraphicsVideoItemPrivate::_q_updateNativeSize() +{ + const QSize &size = surface->surfaceFormat().sizeHint(); + if (nativeSize != size) { + lastFrame = QPixmap(); + nativeSize = size; + + updateRects(); + emit q_ptr->nativeSizeChanged(nativeSize); + } +} + +void QGraphicsVideoItemPrivate::_q_serviceDestroyed() +{ + rendererControl = 0; + service = 0; + + surface->stop(); +} + +void QGraphicsVideoItemPrivate::_q_mediaObjectDestroyed() +{ + mediaObject = 0; + + clearService(); +} + +QGraphicsVideoItem::QGraphicsVideoItem(QGraphicsItem *parent) + : QGraphicsObject(parent) + , d_ptr(new QGraphicsVideoItemPrivate) +{ + d_ptr->q_ptr = this; + d_ptr->surface = new QXVideoSurface; + + setCacheMode(NoCache); + setFlag(QGraphicsItem::ItemIgnoresParentOpacity); + setFlag(QGraphicsItem::ItemSendsGeometryChanges); + setFlag(QGraphicsItem::ItemSendsScenePositionChanges); + + connect(d_ptr->surface, SIGNAL(surfaceFormatChanged(QVideoSurfaceFormat)), + this, SLOT(_q_updateNativeSize())); + + connect(d_ptr->surface, SIGNAL(activeChanged(bool)), this, SLOT(_q_present())); +} + +QGraphicsVideoItem::~QGraphicsVideoItem() +{ + if (d_ptr->rendererControl) { + d_ptr->rendererControl->setSurface(0); + d_ptr->service->releaseControl(d_ptr->rendererControl); + } + + if (d_ptr->currentView) + d_ptr->currentView->setViewportUpdateMode(d_ptr->savedViewportUpdateMode); + + delete d_ptr->surface; + delete d_ptr; +} + +QMediaObject *QGraphicsVideoItem::mediaObject() const +{ + return d_func()->mediaObject; +} + +bool QGraphicsVideoItem::setMediaObject(QMediaObject *object) +{ + Q_D(QGraphicsVideoItem); + + if (object == d->mediaObject) + return true; + + d->clearService(); + + d->mediaObject = object; + + if (d->mediaObject) { + d->service = d->mediaObject->service(); + + if (d->service) { + d->rendererControl = qobject_cast<QVideoRendererControl *>( + d->service->requestControl(QVideoRendererControl_iid)); + + if (d->rendererControl != 0) { + connect(d->service, SIGNAL(destroyed()), this, SLOT(_q_serviceDestroyed())); + d->rendererControl->setSurface(d->surface); + return true; + } + + } + } + + return false; +} + +Qt::AspectRatioMode QGraphicsVideoItem::aspectRatioMode() const +{ + return d_func()->aspectRatioMode; +} + +void QGraphicsVideoItem::setAspectRatioMode(Qt::AspectRatioMode mode) +{ + Q_D(QGraphicsVideoItem); + + d->aspectRatioMode = mode; + d->updateRects(); +} + +QPointF QGraphicsVideoItem::offset() const +{ + return d_func()->rect.topLeft(); +} + +void QGraphicsVideoItem::setOffset(const QPointF &offset) +{ + Q_D(QGraphicsVideoItem); + + d->rect.moveTo(offset); + d->updateRects(); +} + +QSizeF QGraphicsVideoItem::size() const +{ + return d_func()->rect.size(); +} + +void QGraphicsVideoItem::setSize(const QSizeF &size) +{ + Q_D(QGraphicsVideoItem); + + d->rect.setSize(size.isValid() ? size : QSizeF(0, 0)); + d->updateRects(); +} + +QSizeF QGraphicsVideoItem::nativeSize() const +{ + return d_func()->nativeSize; +} + +QRectF QGraphicsVideoItem::boundingRect() const +{ + return d_func()->boundingRect; +} + +void QGraphicsVideoItem::paint( + QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ +#ifdef DEBUG_GFX_VIDEO_ITEM + qDebug() << "QGraphicsVideoItem::paint"; +#endif + + Q_UNUSED(option); + Q_D(QGraphicsVideoItem); + + QGraphicsView *view = 0; + if (scene() && !scene()->views().isEmpty()) + view = scene()->views().first(); + + //it's necessary to switch vieport update mode to FullViewportUpdate + //otherwise the video item area can be just scrolled without notifying overlay + //about geometry changes + if (view != d->currentView) { + if (d->currentView) { + d->currentView->setViewportUpdateMode(d->savedViewportUpdateMode); + } + + d->currentView = view; + if (view) { + d->savedViewportUpdateMode = view->viewportUpdateMode(); + view->setViewportUpdateMode(QGraphicsView::FullViewportUpdate); + } + } + + QColor colorKey = Qt::black; + bool geometryChanged = false; + + if (d->surface) { + if (widget) + d->surface->setWinId(widget->winId()); + + QTransform transform = painter->combinedTransform(); + QRect overlayRect = transform.mapRect(boundingRect()).toRect(); + QRect currentSurfaceRect = d->surface->displayRect(); + + if (widget) { + //workaround for xvideo issue with U/V planes swapped + QPoint topLeft = widget->mapToGlobal(overlayRect.topLeft()); + if ((topLeft.x() & 1) == 0 && topLeft.x() != 0) + overlayRect.moveLeft(overlayRect.left()-1); + } + + d->overlayRect = overlayRect; + + if (currentSurfaceRect != overlayRect) { + if (!d->surface->displayRect().isEmpty()) { + if (d->softwareRenderingEnabled) { + //recalculate scaled frame pixmap if area is resized + if (currentSurfaceRect.size() != overlayRect.size()) { + d->updateLastFrame(); + d->surface->setDisplayRect( overlayRect ); + } + } else { + d->softwareRenderingEnabled = true; + d->updateLastFrame(); + + //don't set new geometry right now, + //but with small delay, to ensure the frame is already + //rendered on top of color key + if (!d->geometryUpdateTimer.isActive()) + d->geometryUpdateTimer.start(GEOMETRY_UPDATE_DELAY, this); + } + } else + d->surface->setDisplayRect( overlayRect ); + + geometryChanged = true; + d->softwareRenderingTimer.start(SOFTWARE_RENDERING_DURATION, this); + +#ifdef DEBUG_GFX_VIDEO_ITEM + qDebug() << "set video display rect:" << overlayRect; +#endif + + } + + colorKey = d->surface->colorKey(); + } + + + if (!d->softwareRenderingEnabled) { + painter->fillRect(d->boundingRect, colorKey); + } else { + if (!d->lastFrame.isNull()) { + painter->drawPixmap(d->boundingRect.topLeft(), d->lastFrame ); + + } else + painter->fillRect(d->boundingRect, Qt::black); + } +} + +QVariant QGraphicsVideoItem::itemChange(GraphicsItemChange change, const QVariant &value) +{ + Q_D(QGraphicsVideoItem); + + if (change == ItemScenePositionHasChanged) { + update(boundingRect()); + } else { + return QGraphicsItem::itemChange(change, value); + } + + return value; +} + +void QGraphicsVideoItem::timerEvent(QTimerEvent *event) +{ + Q_D(QGraphicsVideoItem); + + if (event->timerId() == d->softwareRenderingTimer.timerId() && d->softwareRenderingEnabled) { + d->softwareRenderingTimer.stop(); + d->softwareRenderingEnabled = false; + d->updateLastFrame(); + // repaint last frame, to ensure geometry change is applyed in paused state + d->surface->repaintLastFrame(); + d->_q_present(); + } else if ((event->timerId() == d->geometryUpdateTimer.timerId())) { + d->geometryUpdateTimer.stop(); + //slightly delayed geometry update, + //to avoid flicker at the first geometry change + d->surface->setDisplayRect( d->overlayRect ); + } + + QGraphicsObject::timerEvent(event); +} + +#include "moc_qgraphicsvideoitem.cpp" +QT_END_NAMESPACE |