übernahme Code Shortcut
This commit is contained in:
683
src/videowidget.cpp
Normal file
683
src/videowidget.cpp
Normal file
@@ -0,0 +1,683 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user