684 lines
22 KiB
C++
684 lines
22 KiB
C++
/*
|
|
* Copyright (c) 2011-2025 Meltytech, LLC
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "videowidget.h"
|
|
|
|
#include "Logger.h"
|
|
#include "dialogs/durationdialog.h"
|
|
#include "mainwindow.h"
|
|
#include "qmltypes/qmlfilter.h"
|
|
#include "qmltypes/qmlutilities.h"
|
|
#include "settings.h"
|
|
|
|
#include <Mlt.h>
|
|
#include <QOffscreenSurface>
|
|
#include <QOpenGLContext>
|
|
#include <QQuickItem>
|
|
#include <QUrl>
|
|
#include <QtQml>
|
|
#include <QtWidgets>
|
|
|
|
using namespace Mlt;
|
|
|
|
VideoWidget::VideoWidget(QObject *parent)
|
|
: QQuickWidget(QmlUtilities::sharedEngine(), (QWidget *) parent)
|
|
, Controller()
|
|
, m_grid(0)
|
|
, m_initSem(0)
|
|
, m_isInitialized(false)
|
|
, m_frameRenderer(nullptr)
|
|
, m_zoom(0.0f)
|
|
, m_offset(QPoint(0, 0))
|
|
, m_snapToGrid(true)
|
|
, m_scrubAudio(false)
|
|
, m_maxTextureSize(4096)
|
|
, m_hideVui(false)
|
|
{
|
|
LOG_DEBUG() << "begin";
|
|
setAttribute(Qt::WA_AcceptTouchEvents);
|
|
setResizeMode(QQuickWidget::SizeRootObjectToView);
|
|
setClearColor(palette().window().color());
|
|
QDir importPath = QmlUtilities::qmlDir();
|
|
importPath.cd("modules");
|
|
engine()->addImportPath(importPath.path());
|
|
QmlUtilities::setCommonProperties(rootContext());
|
|
rootContext()->setContextProperty("video", this);
|
|
m_refreshTimer.setInterval(10);
|
|
m_refreshTimer.setSingleShot(true);
|
|
|
|
if (Settings.playerGPU())
|
|
m_glslManager.reset(new Filter(profile(), "glsl.manager"));
|
|
if ((m_glslManager && !m_glslManager->is_valid())) {
|
|
m_glslManager.reset();
|
|
}
|
|
|
|
connect(quickWindow(),
|
|
&QQuickWindow::visibilityChanged,
|
|
this,
|
|
&VideoWidget::setBlankScene,
|
|
Qt::QueuedConnection);
|
|
connect(&m_refreshTimer, &QTimer::timeout, this, &VideoWidget::onRefreshTimeout);
|
|
connect(this, &VideoWidget::rectChanged, this, &VideoWidget::zoomChanged);
|
|
LOG_DEBUG() << "end";
|
|
}
|
|
|
|
VideoWidget::~VideoWidget()
|
|
{
|
|
LOG_DEBUG() << "begin";
|
|
stop();
|
|
if (m_frameRenderer && m_frameRenderer->isRunning()) {
|
|
m_frameRenderer->quit();
|
|
m_frameRenderer->wait();
|
|
m_frameRenderer->deleteLater();
|
|
}
|
|
LOG_DEBUG() << "end";
|
|
}
|
|
|
|
void VideoWidget::initialize()
|
|
{
|
|
LOG_DEBUG() << "begin";
|
|
m_frameRenderer = new FrameRenderer();
|
|
connect(m_frameRenderer,
|
|
&FrameRenderer::frameDisplayed,
|
|
this,
|
|
&VideoWidget::onFrameDisplayed,
|
|
Qt::QueuedConnection);
|
|
connect(m_frameRenderer,
|
|
&FrameRenderer::frameDisplayed,
|
|
this,
|
|
&VideoWidget::frameDisplayed,
|
|
Qt::QueuedConnection);
|
|
connect(m_frameRenderer, SIGNAL(imageReady()), SIGNAL(imageReady()));
|
|
m_initSem.release();
|
|
m_isInitialized = true;
|
|
LOG_DEBUG() << "end";
|
|
}
|
|
|
|
void VideoWidget::renderVideo() {}
|
|
|
|
void VideoWidget::setBlankScene()
|
|
{
|
|
quickWindow()->setColor(palette().window().color());
|
|
setSource(QmlUtilities::blankVui());
|
|
m_savedQmlSource.clear();
|
|
}
|
|
|
|
void VideoWidget::resizeVideo(int width, int height)
|
|
{
|
|
double x, y, w, h;
|
|
double this_aspect = (double) width / height;
|
|
double video_aspect = profile().dar();
|
|
|
|
// Special case optimisation to negate odd effect of sample aspect ratio
|
|
// not corresponding exactly with image resolution.
|
|
if ((int) (this_aspect * 1000) == (int) (video_aspect * 1000)) {
|
|
w = width;
|
|
h = height;
|
|
}
|
|
// Use OpenGL to normalise sample aspect ratio
|
|
else if (height * video_aspect > width) {
|
|
w = width;
|
|
h = width / video_aspect;
|
|
} else {
|
|
w = height * video_aspect;
|
|
h = height;
|
|
}
|
|
x = (width - w) / 2.0;
|
|
y = (height - h) / 2.0;
|
|
m_rect.setRect(x, y, w, h);
|
|
emit rectChanged();
|
|
}
|
|
|
|
void VideoWidget::resizeEvent(QResizeEvent *event)
|
|
{
|
|
QQuickWidget::resizeEvent(event);
|
|
resizeVideo(event->size().width(), event->size().height());
|
|
}
|
|
|
|
void VideoWidget::onRefreshTimeout()
|
|
{
|
|
Controller::refreshConsumer(m_scrubAudio);
|
|
m_scrubAudio = false;
|
|
}
|
|
|
|
void VideoWidget::mousePressEvent(QMouseEvent *event)
|
|
{
|
|
QQuickWidget::mousePressEvent(event);
|
|
if (event->isAccepted())
|
|
return;
|
|
if (event->button() == Qt::LeftButton)
|
|
m_dragStart = event->pos();
|
|
else if (event->button() == Qt::MiddleButton)
|
|
m_mousePosition = event->pos();
|
|
if (MLT.isClip())
|
|
emit dragStarted();
|
|
}
|
|
|
|
void VideoWidget::mouseMoveEvent(QMouseEvent *event)
|
|
{
|
|
QQuickWidget::mouseMoveEvent(event);
|
|
if (event->isAccepted())
|
|
return;
|
|
if (event->buttons() & Qt::MiddleButton) {
|
|
emit offsetChanged(m_offset + m_mousePosition - event->pos());
|
|
m_mousePosition = event->pos();
|
|
return;
|
|
}
|
|
if (event->modifiers() == (Qt::ShiftModifier | Qt::AltModifier) && m_producer) {
|
|
emit seekTo(m_producer->get_length() * event->position().x() / width());
|
|
return;
|
|
}
|
|
if (!(event->buttons() & Qt::LeftButton))
|
|
return;
|
|
if (m_dragStart.isNull())
|
|
return;
|
|
if ((event->pos() - m_dragStart).manhattanLength() < QApplication::startDragDistance())
|
|
return;
|
|
// Reset the drag point to prevent repeating drag actions.
|
|
m_dragStart.setX(0);
|
|
m_dragStart.setY(0);
|
|
if (!MLT.producer())
|
|
return;
|
|
if (MLT.isMultitrack() || MLT.isPlaylist()) {
|
|
MAIN.showStatusMessage(tr("You cannot drag from Project."));
|
|
return;
|
|
} else if (!MLT.isSeekableClip()) {
|
|
MAIN.showStatusMessage(tr("You cannot drag a non-seekable source"));
|
|
return;
|
|
}
|
|
|
|
// Cannot show a DurationDialog during mouse drag
|
|
// This is usually MLT.isLiveProducer(), but that checks for > 1 week,
|
|
// which is too long for the timeline.
|
|
if (m_producer->get_playtime() > qRound(profile().fps() * 24 * 3600)) {
|
|
m_producer->set_in_and_out(0, profile().fps() * 60 - 1);
|
|
}
|
|
|
|
QDrag *drag = new QDrag(this);
|
|
QMimeData *mimeData = new QMimeData;
|
|
mimeData->setData(Mlt::XmlMimeType, MLT.XML().toUtf8());
|
|
drag->setMimeData(mimeData);
|
|
mimeData->setText(QString::number(MLT.producer()->get_playtime()));
|
|
if (m_frameRenderer && m_frameRenderer->getDisplayFrame().is_valid()) {
|
|
Mlt::Frame displayFrame(m_frameRenderer->getDisplayFrame().clone(false, true));
|
|
QImage displayImage
|
|
= MLT.image(&displayFrame, 45 * MLT.profile().dar(), 45).scaledToHeight(45);
|
|
drag->setPixmap(QPixmap::fromImage(displayImage));
|
|
}
|
|
drag->setHotSpot(QPoint(0, 0));
|
|
drag->exec(Qt::CopyAction);
|
|
}
|
|
|
|
void VideoWidget::keyPressEvent(QKeyEvent *event)
|
|
{
|
|
QQuickWidget::keyPressEvent(event);
|
|
if (event->isAccepted())
|
|
return;
|
|
MAIN.keyPressEvent(event);
|
|
}
|
|
|
|
bool VideoWidget::event(QEvent *event)
|
|
{
|
|
bool result = QQuickWidget::event(event);
|
|
if (event->type() == QEvent::PaletteChange && m_sharedFrame.is_valid())
|
|
onFrameDisplayed(m_sharedFrame);
|
|
return result;
|
|
}
|
|
|
|
int VideoWidget::setProducer(Mlt::Producer *producer, bool isMulti)
|
|
{
|
|
int error = Controller::setProducer(producer, isMulti);
|
|
|
|
if (!error) {
|
|
error = reconfigure(isMulti);
|
|
if (!error) {
|
|
// The profile display aspect ratio may have changed.
|
|
resizeVideo(width(), height());
|
|
}
|
|
}
|
|
return error;
|
|
}
|
|
|
|
void VideoWidget::createThread(RenderThread **thread, thread_function_t function, void *data)
|
|
{
|
|
#ifdef Q_OS_WIN
|
|
// On Windows, MLT event consumer-thread-create is fired from the Qt main thread.
|
|
while (!m_isInitialized)
|
|
QCoreApplication::processEvents();
|
|
#else
|
|
if (!m_isInitialized) {
|
|
m_initSem.acquire();
|
|
}
|
|
#endif
|
|
if (!m_renderThread) {
|
|
m_renderThread.reset(new RenderThread(function, data));
|
|
(*thread) = m_renderThread.get();
|
|
(*thread)->start();
|
|
} else {
|
|
m_renderThread->start();
|
|
}
|
|
}
|
|
|
|
static void onThreadCreate(mlt_properties owner, VideoWidget *self, mlt_event_data data)
|
|
{
|
|
Q_UNUSED(owner)
|
|
auto threadData = (mlt_event_data_thread *) Mlt::EventData(data).to_object();
|
|
if (threadData) {
|
|
self->createThread((RenderThread **) threadData->thread,
|
|
threadData->function,
|
|
threadData->data);
|
|
}
|
|
}
|
|
|
|
static void onThreadJoin(mlt_properties owner, VideoWidget *self, mlt_event_data data)
|
|
{
|
|
Q_UNUSED(owner)
|
|
Q_UNUSED(self)
|
|
auto threadData = (mlt_event_data_thread *) Mlt::EventData(data).to_object();
|
|
if (threadData && threadData->thread) {
|
|
auto renderThread = (RenderThread *) *threadData->thread;
|
|
if (renderThread) {
|
|
renderThread->quit();
|
|
renderThread->wait();
|
|
}
|
|
}
|
|
}
|
|
|
|
void VideoWidget::startGlsl()
|
|
{
|
|
if (m_glslManager) {
|
|
m_glslManager->fire_event("init glsl");
|
|
if (!m_glslManager->get_int("glsl_supported")) {
|
|
m_glslManager.reset();
|
|
// Need to destroy MLT global reference to prevent filters from trying to use GPU.
|
|
mlt_properties_clear(mlt_global_properties(), "glslManager");
|
|
emit gpuNotSupported();
|
|
} else {
|
|
emit started();
|
|
}
|
|
}
|
|
}
|
|
|
|
static void onThreadStarted(mlt_properties owner, VideoWidget *self)
|
|
{
|
|
Q_UNUSED(owner)
|
|
self->startGlsl();
|
|
}
|
|
|
|
void VideoWidget::stopGlsl()
|
|
{
|
|
//TODO This is commented out for now because it is causing crashes.
|
|
//Technically, this should be the correct thing to do, but it appears
|
|
//some changes in the 15.01 and 15.03 releases have created regression
|
|
//with respect to restarting the consumer in GPU mode.
|
|
// m_glslManager->fire_event("close glsl");
|
|
}
|
|
|
|
static void onThreadStopped(mlt_properties owner, VideoWidget *self)
|
|
{
|
|
Q_UNUSED(owner)
|
|
self->stopGlsl();
|
|
}
|
|
|
|
int VideoWidget::reconfigure(bool isMulti)
|
|
{
|
|
int error = 0;
|
|
|
|
// use SDL for audio, OpenGL for video
|
|
QString serviceName = property("mlt_service").toString();
|
|
if (!m_consumer || !m_consumer->is_valid()) {
|
|
if (serviceName.isEmpty()) {
|
|
m_consumer.reset(new Mlt::FilteredConsumer(previewProfile(), "sdl2_audio"));
|
|
if (m_consumer->is_valid())
|
|
serviceName = "sdl2_audio";
|
|
else
|
|
serviceName = "rtaudio";
|
|
m_consumer.reset();
|
|
}
|
|
if (isMulti)
|
|
m_consumer.reset(new Mlt::FilteredConsumer(previewProfile(), "multi"));
|
|
else
|
|
m_consumer.reset(
|
|
new Mlt::FilteredConsumer(previewProfile(), serviceName.toLatin1().constData()));
|
|
|
|
m_threadStartEvent.reset();
|
|
m_threadStopEvent.reset();
|
|
m_threadCreateEvent.reset();
|
|
m_threadJoinEvent.reset();
|
|
}
|
|
if (m_consumer->is_valid()) {
|
|
// Connect the producer to the consumer - tell it to "run" later
|
|
if (m_producer && m_producer->is_valid())
|
|
m_consumer->connect(*m_producer);
|
|
// Make an event handler for when a frame's image should be displayed
|
|
m_consumer->listen("consumer-frame-show", this, (mlt_listener) on_frame_show);
|
|
m_consumer->set("real_time", MLT.realTime());
|
|
m_consumer->set("scale", double(Settings.playerPreviewScale()) / MLT.profile().height());
|
|
const int processingMode = property("processing_mode").toInt();
|
|
const bool isDeckLinkHLG = serviceName.startsWith("decklink")
|
|
&& property("decklinkGamma").toInt() == 1;
|
|
switch (processingMode) {
|
|
case ShotcutSettings::Native10Cpu:
|
|
case ShotcutSettings::Linear10Cpu:
|
|
m_consumer->set("mlt_image_format", "rgba64");
|
|
break;
|
|
case ShotcutSettings::Linear10GpuCpu:
|
|
m_consumer->set("mlt_image_format", isDeckLinkHLG ? "yuv444p10" : "rgba64");
|
|
break;
|
|
default: // Native8Cpu
|
|
m_consumer->set("mlt_image_format",
|
|
serviceName.startsWith("decklink") ? "yuv422" : "yuv420p");
|
|
break;
|
|
}
|
|
m_consumer->set("channels", property("audio_channels").toInt());
|
|
if (property("audio_channels").toInt() == 4) {
|
|
m_consumer->set("channel_layout", "quad");
|
|
} else {
|
|
m_consumer->set("channel_layout", "auto");
|
|
}
|
|
switch (MLT.profile().colorspace()) {
|
|
case 601:
|
|
case 170:
|
|
m_consumer->set("color_trc", "smpte170m");
|
|
break;
|
|
case 240:
|
|
m_consumer->set("color_trc", "smpte240m");
|
|
break;
|
|
case 470:
|
|
m_consumer->set("color_trc", "bt470bg");
|
|
break;
|
|
case 2020:
|
|
if (isDeckLinkHLG) {
|
|
m_consumer->set("color_trc", "arib-std-b67");
|
|
} else {
|
|
m_consumer->clear("color_trc");
|
|
}
|
|
break;
|
|
default:
|
|
m_consumer->set("color_trc", "bt709");
|
|
break;
|
|
}
|
|
if (processingMode == ShotcutSettings::Linear10Cpu
|
|
|| (processingMode == ShotcutSettings::Linear10GpuCpu
|
|
&& property("decklinkGamma").toInt() != 1)) {
|
|
m_consumer->set("mlt_color_trc", "linear");
|
|
} else {
|
|
m_consumer->clear("mlt_color_trc");
|
|
}
|
|
if (isMulti) {
|
|
m_consumer->set("terminate_on_pause", 0);
|
|
m_consumer->set("0", serviceName.toLatin1().constData());
|
|
if (!profile().progressive())
|
|
m_consumer->set("0.progressive", property("progressive").toBool());
|
|
m_consumer->set("0.rescale", property("rescale").toString().toLatin1().constData());
|
|
m_consumer->set("0.deinterlacer",
|
|
property("deinterlacer").toString().toLatin1().constData());
|
|
m_consumer->set("0.buffer", qMax(25, qRound(profile().fps())));
|
|
m_consumer->set("0.prefill", 8);
|
|
m_consumer->set("0.drop_max", qRound(profile().fps() / 4.0));
|
|
if (property("keyer").isValid())
|
|
m_consumer->set("0.keyer", property("keyer").toInt());
|
|
m_consumer->set("0.video_delay", Settings.playerVideoDelayMs());
|
|
} else {
|
|
if (!profile().progressive())
|
|
m_consumer->set("progressive", property("progressive").toBool());
|
|
m_consumer->set("rescale", property("rescale").toString().toLatin1().constData());
|
|
m_consumer->set("deinterlacer",
|
|
property("deinterlacer").toString().toLatin1().constData());
|
|
m_consumer->set("buffer", qMax(25, qRound(profile().fps())));
|
|
m_consumer->set("prefill", 8);
|
|
m_consumer->set("drop_max", qRound(profile().fps() / 4.0));
|
|
if (property("keyer").isValid())
|
|
m_consumer->set("keyer", property("keyer").toInt());
|
|
m_consumer->set("video_delay", Settings.playerVideoDelayMs());
|
|
}
|
|
if (m_glslManager) {
|
|
if (!m_threadCreateEvent)
|
|
m_threadCreateEvent.reset(m_consumer->listen("consumer-thread-create",
|
|
this,
|
|
(mlt_listener) onThreadCreate));
|
|
if (!m_threadJoinEvent)
|
|
m_threadJoinEvent.reset(
|
|
m_consumer->listen("consumer-thread-join", this, (mlt_listener) onThreadJoin));
|
|
if (!m_threadStartEvent)
|
|
m_threadStartEvent.reset(m_consumer->listen("consumer-thread-started",
|
|
this,
|
|
(mlt_listener) onThreadStarted));
|
|
if (!m_threadStopEvent)
|
|
m_threadStopEvent.reset(m_consumer->listen("consumer-thread-stopped",
|
|
this,
|
|
(mlt_listener) onThreadStopped));
|
|
} else {
|
|
emit started();
|
|
}
|
|
} else {
|
|
// Cleanup on error
|
|
error = 2;
|
|
Controller::closeConsumer();
|
|
Controller::close();
|
|
}
|
|
return error;
|
|
}
|
|
|
|
void VideoWidget::refreshConsumer(bool scrubAudio)
|
|
{
|
|
scrubAudio |= isPaused() ? scrubAudio : Settings.playerScrubAudio();
|
|
m_scrubAudio |= scrubAudio;
|
|
m_refreshTimer.start();
|
|
}
|
|
|
|
QPoint VideoWidget::offset() const
|
|
{
|
|
if (m_zoom == 0.0) {
|
|
return QPoint(0, 0);
|
|
} else {
|
|
return QPoint(m_offset.x() - (MLT.profile().width() * m_zoom - width()) / 2,
|
|
m_offset.y() - (MLT.profile().height() * m_zoom - height()) / 2);
|
|
}
|
|
}
|
|
|
|
QImage VideoWidget::image() const
|
|
{
|
|
SharedFrame frame = m_frameRenderer->getDisplayFrame();
|
|
if (frame.is_valid()) {
|
|
const uint8_t *image = frame.get_image(mlt_image_rgba);
|
|
if (image) {
|
|
int width = frame.get_image_width();
|
|
int height = frame.get_image_height();
|
|
QImage temp(image, width, height, QImage::Format_RGBA8888);
|
|
return temp.copy();
|
|
}
|
|
}
|
|
return QImage();
|
|
}
|
|
|
|
bool VideoWidget::imageIsProxy() const
|
|
{
|
|
bool isProxy = false;
|
|
SharedFrame frame = m_frameRenderer->getDisplayFrame();
|
|
if (frame.is_valid()) {
|
|
Mlt::Producer *frameProducer = frame.get_original_producer();
|
|
if (frameProducer && frameProducer->is_valid() && frameProducer->get_int(kIsProxyProperty)) {
|
|
isProxy = true;
|
|
}
|
|
delete frameProducer;
|
|
}
|
|
return isProxy;
|
|
}
|
|
|
|
void VideoWidget::requestImage() const
|
|
{
|
|
m_frameRenderer->requestImage();
|
|
}
|
|
|
|
void VideoWidget::toggleVuiDisplay()
|
|
{
|
|
m_hideVui = !m_hideVui;
|
|
refreshConsumer();
|
|
}
|
|
|
|
void VideoWidget::onFrameDisplayed(const SharedFrame &frame)
|
|
{
|
|
m_mutex.lock();
|
|
m_sharedFrame = frame;
|
|
m_mutex.unlock();
|
|
bool isVui = frame.get_int(kShotcutVuiMetaProperty) && !m_hideVui;
|
|
if (!isVui && source() != QmlUtilities::blankVui()) {
|
|
m_savedQmlSource = source();
|
|
setSource(QmlUtilities::blankVui());
|
|
} else if (isVui && !m_savedQmlSource.isEmpty() && source() != m_savedQmlSource) {
|
|
setSource(m_savedQmlSource);
|
|
}
|
|
quickWindow()->update();
|
|
}
|
|
|
|
void VideoWidget::setGrid(int grid)
|
|
{
|
|
m_grid = grid;
|
|
emit gridChanged();
|
|
quickWindow()->update();
|
|
}
|
|
|
|
void VideoWidget::setZoom(float zoom)
|
|
{
|
|
m_zoom = zoom;
|
|
emit zoomChanged();
|
|
// Reset the VUI control
|
|
setSource(source());
|
|
quickWindow()->update();
|
|
}
|
|
|
|
void VideoWidget::setOffsetX(int x)
|
|
{
|
|
m_offset.setX(x);
|
|
emit offsetChanged();
|
|
quickWindow()->update();
|
|
}
|
|
|
|
void VideoWidget::setOffsetY(int y)
|
|
{
|
|
m_offset.setY(y);
|
|
emit offsetChanged();
|
|
quickWindow()->update();
|
|
}
|
|
|
|
void VideoWidget::setCurrentFilter(QmlFilter *filter, QmlMetadata *meta)
|
|
{
|
|
m_hideVui = false;
|
|
if (meta && meta->type() == QmlMetadata::Filter
|
|
&& QFile::exists(meta->vuiFilePath().toLocalFile())) {
|
|
filter->producer().set(kShotcutVuiMetaProperty, 1);
|
|
rootContext()->setContextProperty("filter", filter);
|
|
setSource(meta->vuiFilePath());
|
|
refreshConsumer();
|
|
} else {
|
|
setBlankScene();
|
|
}
|
|
}
|
|
|
|
void VideoWidget::setSnapToGrid(bool snap)
|
|
{
|
|
m_snapToGrid = snap;
|
|
emit snapToGridChanged();
|
|
}
|
|
|
|
// MLT consumer-frame-show event handler
|
|
void VideoWidget::on_frame_show(mlt_consumer, VideoWidget *widget, mlt_event_data data)
|
|
{
|
|
auto frame = Mlt::EventData(data).to_frame();
|
|
if (frame.is_valid() && frame.get_int("rendered")) {
|
|
int timeout = (widget->consumer()->get_int("real_time") > 0) ? 0 : 1000;
|
|
if (widget->m_frameRenderer
|
|
&& widget->m_frameRenderer->semaphore()->tryAcquire(1, timeout)) {
|
|
QMetaObject::invokeMethod(widget->m_frameRenderer,
|
|
"showFrame",
|
|
Qt::QueuedConnection,
|
|
Q_ARG(Mlt::Frame, frame));
|
|
} else if (!Settings.playerRealtime()) {
|
|
LOG_WARNING() << "VideoWidget dropped frame" << frame.get_position();
|
|
}
|
|
}
|
|
}
|
|
|
|
RenderThread::RenderThread(thread_function_t function, void *data)
|
|
: QThread{nullptr}
|
|
, m_function{function}
|
|
, m_data{data}
|
|
, m_context{new QOpenGLContext}
|
|
, m_surface{new QOffscreenSurface}
|
|
{
|
|
QSurfaceFormat format;
|
|
format.setProfile(QSurfaceFormat::CoreProfile);
|
|
format.setMajorVersion(3);
|
|
format.setMinorVersion(2);
|
|
format.setDepthBufferSize(0);
|
|
format.setStencilBufferSize(0);
|
|
m_context->setFormat(format);
|
|
m_context->create();
|
|
m_context->moveToThread(this);
|
|
m_surface->setFormat(format);
|
|
m_surface->create();
|
|
}
|
|
|
|
RenderThread::~RenderThread()
|
|
{
|
|
m_surface->destroy();
|
|
}
|
|
|
|
void RenderThread::run()
|
|
{
|
|
Q_ASSERT(m_context->isValid());
|
|
m_context->makeCurrent(m_surface.get());
|
|
m_function(m_data);
|
|
m_context->doneCurrent();
|
|
}
|
|
|
|
FrameRenderer::FrameRenderer()
|
|
: QThread(nullptr)
|
|
, m_semaphore(3)
|
|
, m_imageRequested(false)
|
|
{
|
|
setObjectName("FrameRenderer");
|
|
moveToThread(this);
|
|
start();
|
|
}
|
|
|
|
FrameRenderer::~FrameRenderer() {}
|
|
|
|
void FrameRenderer::showFrame(Mlt::Frame frame)
|
|
{
|
|
m_displayFrame = SharedFrame(frame);
|
|
emit frameDisplayed(m_displayFrame);
|
|
|
|
if (m_imageRequested) {
|
|
m_imageRequested = false;
|
|
emit imageReady();
|
|
}
|
|
|
|
m_semaphore.release();
|
|
}
|
|
|
|
void FrameRenderer::requestImage()
|
|
{
|
|
m_imageRequested = true;
|
|
}
|
|
|
|
SharedFrame FrameRenderer::getDisplayFrame()
|
|
{
|
|
return m_displayFrame;
|
|
}
|