Warning, file /libraries/phonon-vlc/src/video/videowidget.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 Copyright (C) 2007-2008 Tanguy Krotoff <tkrotoff@gmail.com> 0003 Copyright (C) 2008 Lukas Durfina <lukas.durfina@gmail.com> 0004 Copyright (C) 2009 Fathi Boudra <fabo@kde.org> 0005 Copyright (C) 2009-2011 vlc-phonon AUTHORS <kde-multimedia@kde.org> 0006 Copyright (C) 2011-2021 Harald Sitter <sitter@kde.org> 0007 0008 This library is free software; you can redistribute it and/or 0009 modify it under the terms of the GNU Lesser General Public 0010 License as published by the Free Software Foundation; either 0011 version 2.1 of the License, or (at your option) any later version. 0012 0013 This library is distributed in the hope that it will be useful, 0014 but WITHOUT ANY WARRANTY; without even the implied warranty of 0015 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0016 Lesser General Public License for more details. 0017 0018 You should have received a copy of the GNU Lesser General Public 0019 License along with this library. If not, see <http://www.gnu.org/licenses/>. 0020 */ 0021 0022 #include "videowidget.h" 0023 0024 #include <QGuiApplication> 0025 #include <QPainter> 0026 #include <QPaintEvent> 0027 0028 #include <vlc/vlc.h> 0029 0030 #include "utils/debug.h" 0031 #include "mediaobject.h" 0032 #include "media.h" 0033 0034 #include "video/videomemorystream.h" 0035 0036 namespace Phonon { 0037 namespace VLC { 0038 0039 #define DEFAULT_QSIZE QSize(320, 240) 0040 0041 class SurfacePainter : public VideoMemoryStream 0042 { 0043 public: 0044 void handlePaint(QPaintEvent *event) 0045 { 0046 // Mind that locking here is still faster than making this lockfree by 0047 // dispatching QEvents. 0048 // Plus VLC can actually skip frames as necessary. 0049 QMutexLocker lock(&m_mutex); 0050 Q_UNUSED(event); 0051 0052 if (m_frame.isNull()) { 0053 return; 0054 } 0055 0056 QPainter painter(widget); 0057 // When using OpenGL for the QPaintEngine drawing the same QImage twice 0058 // does not actually result in a texture change for one reason or another. 0059 // So we simply create new images for every event. This is plenty cheap 0060 // as the QImage only points to the plane data (it can't even make it 0061 // properly shared as it does not know that the data belongs to a QBA). 0062 // TODO: investigate if this is still necessary. This was added for gwenview, but with Qt 5.15 the problem 0063 // can't be produced. 0064 painter.drawImage(drawFrameRect(), QImage(m_frame)); 0065 event->accept(); 0066 } 0067 0068 VideoWidget *widget; 0069 0070 private: 0071 void *lockCallback(void **planes) override 0072 { 0073 m_mutex.lock(); 0074 planes[0] = (void *) m_frame.bits(); 0075 return 0; 0076 } 0077 0078 void unlockCallback(void *picture,void *const *planes) override 0079 { 0080 Q_UNUSED(picture); 0081 Q_UNUSED(planes); 0082 m_mutex.unlock(); 0083 } 0084 0085 void displayCallback(void *picture) override 0086 { 0087 Q_UNUSED(picture); 0088 if (widget) 0089 widget->update(); 0090 } 0091 0092 unsigned formatCallback(char *chroma, 0093 unsigned *width, unsigned *height, 0094 unsigned *pitches, 0095 unsigned *lines) override 0096 { 0097 QMutexLocker lock(&m_mutex); 0098 // Surface rendering is a fallback system used when no efficient rendering implementation is available. 0099 // As such we only support RGB32 for simplicity reasons and this will almost always mean software scaling. 0100 // And since scaling is unavoidable anyway we take the canonical frame size and then scale it on our end via 0101 // QPainter, again, greater simplicity at likely no real extra cost since this is all super inefficient anyway. 0102 // Also, since aspect ratio can be change mid-playback by the user, doing the scaling on our end means we 0103 // don't need to restart the entire player to retrigger format calculation. 0104 // With all that in mind we simply use the canonical size and feed VLC the QImage's pitch and lines as 0105 // effectively the VLC vout is the QImage so its constraints matter. 0106 0107 // per https://wiki.videolan.org/Hacker_Guide/Video_Filters/#Pitch.2C_visible_pitch.2C_planes_et_al. 0108 // it would seem that we can use either real or visible pitches and lines as VLC generally will iterate the 0109 // smallest value when moving data between two entities. i.e. since QImage will at most paint NxM anyway, 0110 // we may just go with its values as calculating the real pitch/line of the VLC picture_t for RV32 wouldn't 0111 // change the maximum pitch/lines we can paint on the output side. 0112 0113 qstrcpy(chroma, "RV32"); 0114 m_frame = QImage(*width, *height, QImage::Format_RGB32); 0115 Q_ASSERT(!m_frame.isNull()); // ctor may construct null if allocation fails 0116 m_frame.fill(0); 0117 pitches[0] = m_frame.bytesPerLine(); 0118 lines[0] = m_frame.sizeInBytes() / m_frame.bytesPerLine(); 0119 0120 return m_frame.sizeInBytes(); 0121 } 0122 0123 void formatCleanUpCallback() override 0124 { 0125 // Lazy delete the object to avoid callbacks from VLC after deletion. 0126 if (!widget) { 0127 // The widget member is set to null by the widget destructor, so when this condition is true the 0128 // widget had already been destroyed and we can't possibly receive a paint event anymore, meaning 0129 // we need no lock here. If it were any other way we'd have trouble with synchronizing deletion 0130 // without deleting a locked mutex. 0131 delete this; 0132 } 0133 } 0134 0135 QRect scaleToAspect(QRect srcRect, int w, int h) const 0136 { 0137 float width = srcRect.width(); 0138 float height = srcRect.width() * (float(h) / float(w)); 0139 if (height > srcRect.height()) { 0140 height = srcRect.height(); 0141 width = srcRect.height() * (float(w) / float(h)); 0142 } 0143 return QRect(0, 0, (int)width, (int)height); 0144 } 0145 0146 QRect drawFrameRect() const 0147 { 0148 QRect widgetRect = widget->rect(); 0149 QRect drawFrameRect; 0150 switch (widget->aspectRatio()) { 0151 case Phonon::VideoWidget::AspectRatioWidget: 0152 drawFrameRect = widgetRect; 0153 // No more calculations needed. 0154 return drawFrameRect; 0155 case Phonon::VideoWidget::AspectRatio4_3: 0156 drawFrameRect = scaleToAspect(widgetRect, 4, 3); 0157 break; 0158 case Phonon::VideoWidget::AspectRatio16_9: 0159 drawFrameRect = scaleToAspect(widgetRect, 16, 9); 0160 break; 0161 case Phonon::VideoWidget::AspectRatioAuto: 0162 drawFrameRect = QRect(0, 0, m_frame.width(), m_frame.height()); 0163 break; 0164 } 0165 0166 // Scale m_drawFrameRect to fill the widget 0167 // without breaking aspect: 0168 float widgetWidth = widgetRect.width(); 0169 float widgetHeight = widgetRect.height(); 0170 float frameWidth = widgetWidth; 0171 float frameHeight = drawFrameRect.height() * float(widgetWidth) / float(drawFrameRect.width()); 0172 0173 switch (widget->scaleMode()) { 0174 case Phonon::VideoWidget::ScaleAndCrop: 0175 if (frameHeight < widgetHeight) { 0176 frameWidth *= float(widgetHeight) / float(frameHeight); 0177 frameHeight = widgetHeight; 0178 } 0179 break; 0180 case Phonon::VideoWidget::FitInView: 0181 if (frameHeight > widgetHeight) { 0182 frameWidth *= float(widgetHeight) / float(frameHeight); 0183 frameHeight = widgetHeight; 0184 } 0185 break; 0186 } 0187 drawFrameRect.setSize(QSize(int(frameWidth), int(frameHeight))); 0188 drawFrameRect.moveTo(int((widgetWidth - frameWidth) / 2.0f), 0189 int((widgetHeight - frameHeight) / 2.0f)); 0190 return drawFrameRect; 0191 } 0192 0193 // Could ReadWriteLock two frames so VLC can write while we paint. 0194 QImage m_frame; 0195 QMutex m_mutex; 0196 }; 0197 0198 VideoWidget::VideoWidget(QWidget *parent) : 0199 BaseWidget(parent), 0200 SinkNode(), 0201 m_videoSize(DEFAULT_QSIZE), 0202 m_aspectRatio(Phonon::VideoWidget::AspectRatioAuto), 0203 m_scaleMode(Phonon::VideoWidget::FitInView), 0204 m_filterAdjustActivated(false), 0205 m_brightness(0.0), 0206 m_contrast(0.0), 0207 m_hue(0.0), 0208 m_saturation(0.0), 0209 m_surfacePainter(0) 0210 { 0211 // We want background painting so Qt autofills with black. 0212 setAttribute(Qt::WA_NoSystemBackground, false); 0213 0214 // Required for dvdnav 0215 #ifdef __GNUC__ 0216 #warning dragonplayer munches on our mouse events, so clicking in a DVD menu does not work - vlc 1.2 where are thu? 0217 #endif // __GNUC__ 0218 setMouseTracking(true); 0219 0220 // setBackgroundColor 0221 QPalette p = palette(); 0222 p.setColor(backgroundRole(), Qt::black); 0223 setPalette(p); 0224 setAutoFillBackground(true); 0225 } 0226 0227 VideoWidget::~VideoWidget() 0228 { 0229 if (m_surfacePainter) 0230 m_surfacePainter->widget = 0; // Lazy delete 0231 } 0232 0233 void VideoWidget::handleConnectToMediaObject(MediaObject *mediaObject) 0234 { 0235 connect(mediaObject, SIGNAL(hasVideoChanged(bool)), 0236 SLOT(updateVideoSize(bool))); 0237 connect(mediaObject, SIGNAL(hasVideoChanged(bool)), 0238 SLOT(processPendingAdjusts(bool))); 0239 connect(mediaObject, SIGNAL(currentSourceChanged(MediaSource)), 0240 SLOT(clearPendingAdjusts())); 0241 0242 clearPendingAdjusts(); 0243 } 0244 0245 void VideoWidget::handleDisconnectFromMediaObject(MediaObject *mediaObject) 0246 { 0247 // Undo all connections or path creation->destruction->creation can cause 0248 // duplicated connections or getting signals from two different MediaObjects. 0249 disconnect(mediaObject, 0, this, 0); 0250 } 0251 0252 void VideoWidget::handleAddToMedia(Media *media) 0253 { 0254 media->addOption(":video"); 0255 0256 if (!m_surfacePainter) { 0257 #if defined(Q_OS_MAC) 0258 m_player->setNsObject(cocoaView()); 0259 #elif defined(Q_OS_UNIX) 0260 if (QGuiApplication::platformName().contains(QStringLiteral("xcb"), Qt::CaseInsensitive)) { 0261 m_player->setXWindow(winId()); 0262 } else { 0263 enableSurfacePainter(); 0264 } 0265 #elif defined(Q_OS_WIN) 0266 m_player->setHwnd((HWND)winId()); 0267 #endif 0268 } 0269 } 0270 0271 Phonon::VideoWidget::AspectRatio VideoWidget::aspectRatio() const 0272 { 0273 return m_aspectRatio; 0274 } 0275 0276 void VideoWidget::setAspectRatio(Phonon::VideoWidget::AspectRatio aspect) 0277 { 0278 DEBUG_BLOCK; 0279 if (!m_player) 0280 return; 0281 0282 m_aspectRatio = aspect; 0283 0284 switch (m_aspectRatio) { 0285 // FIXME: find a way to implement aspectratiowidget, it is meant to scale 0286 // and stretch (i.e. scale to window without retaining aspect ratio). 0287 case Phonon::VideoWidget::AspectRatioAuto: 0288 m_player->setVideoAspectRatio(QByteArray()); 0289 return; 0290 case Phonon::VideoWidget::AspectRatio4_3: 0291 m_player->setVideoAspectRatio("4:3"); 0292 return; 0293 case Phonon::VideoWidget::AspectRatio16_9: 0294 m_player->setVideoAspectRatio("16:9"); 0295 return; 0296 } 0297 warning() << "The aspect ratio" << aspect << "is not supported by Phonon VLC."; 0298 } 0299 0300 Phonon::VideoWidget::ScaleMode VideoWidget::scaleMode() const 0301 { 0302 return m_scaleMode; 0303 } 0304 0305 void VideoWidget::setScaleMode(Phonon::VideoWidget::ScaleMode scale) 0306 { 0307 #ifdef __GNUC__ 0308 #warning OMG WTF 0309 #endif 0310 m_scaleMode = scale; 0311 switch (m_scaleMode) { 0312 } 0313 warning() << "The scale mode" << scale << "is not supported by Phonon VLC."; 0314 } 0315 0316 qreal VideoWidget::brightness() const 0317 { 0318 return m_brightness; 0319 } 0320 0321 void VideoWidget::setBrightness(qreal brightness) 0322 { 0323 DEBUG_BLOCK; 0324 if (!m_player) { 0325 return; 0326 } 0327 if (!enableFilterAdjust()) { 0328 // Add to pending adjusts 0329 m_pendingAdjusts.insert(QByteArray("setBrightness"), brightness); 0330 return; 0331 } 0332 0333 // VLC operates within a 0.0 to 2.0 range for brightness. 0334 m_brightness = brightness; 0335 m_player->setVideoAdjust(libvlc_adjust_Brightness, 0336 phononRangeToVlcRange(m_brightness, 2.0)); 0337 } 0338 0339 qreal VideoWidget::contrast() const 0340 { 0341 return m_contrast; 0342 } 0343 0344 void VideoWidget::setContrast(qreal contrast) 0345 { 0346 DEBUG_BLOCK; 0347 if (!m_player) { 0348 return; 0349 } 0350 if (!enableFilterAdjust()) { 0351 // Add to pending adjusts 0352 m_pendingAdjusts.insert(QByteArray("setContrast"), contrast); 0353 return; 0354 } 0355 0356 // VLC operates within a 0.0 to 2.0 range for contrast. 0357 m_contrast = contrast; 0358 m_player->setVideoAdjust(libvlc_adjust_Contrast, phononRangeToVlcRange(m_contrast, 2.0)); 0359 } 0360 0361 qreal VideoWidget::hue() const 0362 { 0363 return m_hue; 0364 } 0365 0366 void VideoWidget::setHue(qreal hue) 0367 { 0368 DEBUG_BLOCK; 0369 if (!m_player) { 0370 return; 0371 } 0372 if (!enableFilterAdjust()) { 0373 // Add to pending adjusts 0374 m_pendingAdjusts.insert(QByteArray("setHue"), hue); 0375 return; 0376 } 0377 0378 // VLC operates within a 0 to 360 range for hue. 0379 // Phonon operates on -1.0 to 1.0, so we need to consider 0 to 180 as 0380 // 0 to 1.0 and 180 to 360 as -1 to 0.0. 0381 // 360/0 (0) 0382 // ___ 0383 // / \ 0384 // 270 (-.25) | | 90 (.25) 0385 // \___/ 0386 // 180 (1/-1) 0387 // (-.25 is 360 minus 90 (vlcValue of .25). 0388 m_hue = hue; 0389 const int vlcValue = static_cast<int>(phononRangeToVlcRange(qAbs(hue), 180.0, false)); 0390 int value = 0; 0391 if (hue >= 0) 0392 value = vlcValue; 0393 else 0394 value = 360.0 - vlcValue; 0395 m_player->setVideoAdjust(libvlc_adjust_Hue, value); 0396 } 0397 0398 qreal VideoWidget::saturation() const 0399 { 0400 return m_saturation; 0401 } 0402 0403 void VideoWidget::setSaturation(qreal saturation) 0404 { 0405 DEBUG_BLOCK; 0406 if (!m_player) { 0407 return; 0408 } 0409 if (!enableFilterAdjust()) { 0410 // Add to pending adjusts 0411 m_pendingAdjusts.insert(QByteArray("setSaturation"), saturation); 0412 return; 0413 } 0414 0415 // VLC operates within a 0.0 to 3.0 range for saturation. 0416 m_saturation = saturation; 0417 m_player->setVideoAdjust(libvlc_adjust_Saturation, 0418 phononRangeToVlcRange(m_saturation, 3.0)); 0419 } 0420 0421 QWidget *VideoWidget::widget() 0422 { 0423 return this; 0424 } 0425 0426 QSize VideoWidget::sizeHint() const 0427 { 0428 return m_videoSize; 0429 } 0430 0431 void VideoWidget::updateVideoSize(bool hasVideo) 0432 { 0433 if (hasVideo) { 0434 m_videoSize = m_player->videoSize(); 0435 updateGeometry(); 0436 update(); 0437 } else 0438 m_videoSize = DEFAULT_QSIZE; 0439 } 0440 0441 void VideoWidget::setVisible(bool visible) 0442 { 0443 if (window() && window()->testAttribute(Qt::WA_DontShowOnScreen) && !m_surfacePainter) { 0444 enableSurfacePainter(); 0445 } 0446 QWidget::setVisible(visible); 0447 } 0448 0449 void VideoWidget::processPendingAdjusts(bool videoAvailable) 0450 { 0451 if (!videoAvailable || !m_mediaObject || !m_mediaObject->hasVideo()) { 0452 return; 0453 } 0454 0455 QHashIterator<QByteArray, qreal> it(m_pendingAdjusts); 0456 while (it.hasNext()) { 0457 it.next(); 0458 QMetaObject::invokeMethod(this, it.key().constData(), Q_ARG(qreal, it.value())); 0459 } 0460 m_pendingAdjusts.clear(); 0461 } 0462 0463 void VideoWidget::clearPendingAdjusts() 0464 { 0465 m_pendingAdjusts.clear(); 0466 } 0467 0468 void VideoWidget::paintEvent(QPaintEvent *event) 0469 { 0470 Q_UNUSED(event); 0471 if (m_surfacePainter) 0472 m_surfacePainter->handlePaint(event); 0473 } 0474 0475 bool VideoWidget::enableFilterAdjust(bool adjust) 0476 { 0477 DEBUG_BLOCK; 0478 // Need to check for MO here, because we can get called before a VOut is actually 0479 // around in which case we just ignore this. 0480 if (!m_mediaObject || !m_mediaObject->hasVideo()) { 0481 debug() << "no mo or no video!!!"; 0482 return false; 0483 } 0484 if ((!m_filterAdjustActivated && adjust) || 0485 (m_filterAdjustActivated && !adjust)) { 0486 debug() << "adjust: " << adjust; 0487 m_player->setVideoAdjust(libvlc_adjust_Enable, static_cast<int>(adjust)); 0488 m_filterAdjustActivated = adjust; 0489 } 0490 return true; 0491 } 0492 0493 float VideoWidget::phononRangeToVlcRange(qreal phononValue, float upperBoundary, 0494 bool shift) 0495 { 0496 // VLC operates on different ranges than Phonon. Phonon always uses a range of 0497 // -1:1 with 0 as the default value. 0498 // It is therefore necessary to convert between the two schemes using sophisticated magic. 0499 // First the incoming range is locked between -1..1, then depending on shift 0500 // either normalized to 0..2 or 0..1 and finally a new value is calculated 0501 // depending on the upperBoundary and the normalized range. 0502 float value = static_cast<float>(phononValue); 0503 float range = 2.0; // The default normalized range will be 0..2 = 2 0504 0505 // Ensure valid range 0506 if (value < -1.0) 0507 value = -1.0; 0508 else if (value > 1.0) 0509 value = 1.0; 0510 0511 if (shift) 0512 value += 1.0; // Shift into 0..2 range 0513 else { 0514 // Chop negative value; normalize to 0..1 = range 1 0515 if (value < 0.0) 0516 value = 0.0; 0517 range = 1.0; 0518 } 0519 0520 return (value * (upperBoundary/range)); 0521 } 0522 0523 QImage VideoWidget::snapshot() const 0524 { 0525 DEBUG_BLOCK; 0526 if (m_player) 0527 return m_player->snapshot(); 0528 else 0529 return QImage(); 0530 } 0531 0532 void VideoWidget::enableSurfacePainter() 0533 { 0534 if (m_surfacePainter) { 0535 return; 0536 } 0537 0538 debug() << "ENABLING SURFACE PAINTING"; 0539 m_surfacePainter = new SurfacePainter; 0540 m_surfacePainter->widget = this; 0541 m_surfacePainter->setCallbacks(m_player); 0542 } 0543 0544 } // namespace VLC 0545 } // namespace Phonon