/**************************************************************************** ** ** 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 #include #include #include #include #include #include #include #include "qgraphicsvideoitem.h" #include #include #include #include #include #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 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; yupdate(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( 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