13#include <QMutexLocker>
14#include <QElapsedTimer>
15#include <QWaitCondition>
27#include <mlt++/MltConsumer.h>
28#include <mlt++/MltFrame.h>
29#include <mlt++/MltFilter.h>
30#include <mlt-7/framework/mlt_service.h>
46#ifdef MLT_LOG_REDIRECTION
47void qt_redirection_callback(
void *ptr,
int level,
const char *fmt, va_list vl)
49 static int print_prefix = 1;
50 mlt_properties properties = ptr ? MLT_SERVICE_PROPERTIES((mlt_service) ptr) : NULL;
52 if (level > mlt_log_get_level())
55 static const int prefix_size = 200;
56 char prefix[prefix_size] =
"";
58 if (print_prefix && properties) {
59 char *mlt_type = mlt_properties_get(properties,
"mlt_type");
60 char *mlt_service = mlt_properties_get(properties,
"mlt_service");
61 char *resource = mlt_properties_get(properties,
"resource");
63 if (!(resource && *resource && resource[0] ==
'<' && resource[strlen(resource) - 1] ==
'>'))
64 mlt_type = mlt_properties_get(properties,
"mlt_type");
66 snprintf(prefix, prefix_size,
"[%s %s] ", mlt_type, mlt_service);
68 snprintf(prefix, prefix_size,
"[%s %p] ", mlt_type, ptr);
70 snprintf(prefix, prefix_size,
"%s\n ", resource);
71 qDebug().nospace() << qPrintable(prefix);
73 print_prefix = strstr(fmt,
"\n") != NULL;
74 vsnprintf(prefix, prefix_size, fmt, vl);
75 qDebug().nospace() << qPrintable(prefix);
91struct FrameRenderingStats
93 static constexpr int frameStatsWindow = 50;
97 int lastRenderedFrame {-1};
98 QElapsedTimer timeSinceLastFrame;
101 averageFrameDuration.
reset(frameStatsWindow);
102 droppedFramesCount.reset(frameStatsWindow);
103 lastRenderedFrame = -1;
115 Mlt::Frame frame(p_frame);
116 Mlt::Consumer consumer(c);
117 const int position = frame.get_position();
158#ifdef MLT_LOG_REDIRECTION
159 mlt_log_set_level(MLT_LOG_VERBOSE);
160 mlt_log_set_callback(&qt_redirection_callback);
166 profile.reset(
new Mlt::Profile());
167 profile->set_frame_rate(24, 1);
189 Mlt::Factory::close();
202 for (
int i = 0; i < SCRUB_AUDIO_WINDOW; i++ ) {
309 if (requireFullRestart) {
388 m_d->sigPushAudioCompressor->start(frameIndex);
405 if (file.has_value()) {
409 new Mlt::Producer(*
m_d->profile,
413 new Mlt::Producer(*
m_d->profile,
"krita_play_chunk", file->absoluteFilePath().toUtf8().data()));
415 if (producer->is_valid()) {
423 qDebug() <<
"Warning: Invalid MLT producer for file: " <<
ppVar(file->absoluteFilePath()) <<
" Falling back to audio-less playback.";
434 producer->set(
"limit_enabled",
false);
435 producer->set(
"speed",
m_d->playbackSpeed);
450 if (animationState) {
451 this->disconnect(animationState);
452 animationState->disconnect(
this);
457 if (image && image->animationInterface()) {
458 this->disconnect(image->animationInterface());
459 image->animationInterface()->disconnect(
this);
479 if (animationState) {
485 m_d->sigSetPlaybackSpeed->start(
value);
496 StopAndResume callbackStopResume(m_d.data());
497 m_d->profile->set_frame_rate(activeCanvas()->image()->animationInterface()->framerate(), 1);
507 KisCanvasAnimationState* animationState = activeCanvas()->animationState();
508 if (animationState) {
509 setupProducer(animationState->mediaInfo());
514 m_d->profile->set_frame_rate(
activeCanvas()->image()->animationInterface()->framerate(), 1);
517 QSharedPointer<Mlt::Producer> producer = m_d->canvasProducers[activeCanvas()];
518 auto image = activeCanvas()->image();
519 KIS_SAFE_ASSERT_RECOVER_RETURN(image);
520 producer->set(
"start_frame", image->animationInterface()->activePlaybackRange().start());
521 producer->set(
"end_frame", image->animationInterface()->activePlaybackRange().end());
541 for (
auto it =
m_d->canvasProducers.begin(); it !=
m_d->canvasProducers.end(); ++it) {
542 if (it.key() == canvas) {
543 m_d->canvasProducers.erase(it);
554 if (
m_d->frameStats.lastRenderedFrame < 0) {
555 m_d->frameStats.timeSinceLastFrame.start();
557 const int droppedFrames = qMax(0, frame -
m_d->frameStats.lastRenderedFrame - 1);
558 m_d->frameStats.averageFrameDuration(
m_d->frameStats.timeSinceLastFrame.restart());
559 m_d->frameStats.droppedFramesCount(droppedFrames);
561 m_d->frameStats.lastRenderedFrame = frame;
567 QMutexLocker l(&
m_d->frameWaitingInterface.renderingControlMutex);
568 m_d->frameWaitingInterface.waitingForFrame =
false;
569 m_d->frameWaitingInterface.renderingWaitCondition.wakeAll();
576 m_d->playbackSpeed = speed;
582 m_d->pullConsumer->set(
"volume", 0.0);
583 m_d->pushConsumer->set(
"volume", 0.0);
585 m_d->pullConsumer->set(
"volume", volumeNormalized);
586 m_d->pushConsumer->set(
"volume", volumeNormalized);
592 return &
m_d->frameWaitingInterface;
625 const int droppedFrames =
m_d->frameStats.droppedFramesCount.rollingSum();
626 const int totalFrames =
627 m_d->frameStats.droppedFramesCount.rollingCount() +
633 const qreal avgTimePerFrame =
m_d->frameStats.averageFrameDuration.rollingMeanSafe();
float value(const T *src, size_t ch)
void registerKritaMLTProducer(Mlt::Repository *repository)
static void mltOnConsumerFrameShow(mlt_consumer c, void *p_self, mlt_frame p_frame)
const float SCRUB_AUDIO_SECONDS
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
static QString getFileFromContentUri(QString contentUri)
KisCanvasAnimationState * animationState() const
KisImageWSP image() const
The KisCanvasAnimationState class stores all of the canvas-specific animation state.
void sigAudioLevelChanged(qreal value)
void sigPlaybackStateChanged(PlaybackState state)
boost::optional< QFileInfo > mediaInfo()
Get the media file info associated with this canvas, if available.
PlaybackState playbackState()
void sigPlaybackMediaChanged()
qreal playbackSpeed() const
void showFrame(int frame, bool finalize=false)
void sigPlaybackSpeedChanged(qreal value)
void sigFramerateChanged()
const KisTimeSpan & activePlaybackRange() const
activePlaybackRange
void sigPlaybackRangeChanged()
const KisTimeSpan & documentPlaybackRange() const
documentPlaybackRange
KisImageAnimationInterface * animationInterface() const
The KisPlaybackEngineMLT class is an implementation of KisPlaybackEngine that uses MLT (Media Lovin' ...
void sigChangeActiveCanvasFrame(int p_frame)
void setCanvas(KoCanvasBase *canvas) override
PlaybackStats playbackStatistics() const override
QScopedPointer< Private > m_d
void setMute(bool val) override
void throttledShowFrame(const int frame)
throttledShowFrame
void throttledSetSpeed(const double speed)
throttledSetSpeed
void canvasDestroyed(QObject *canvas)
void seek(int frameIndex, SeekOptionFlags flags=SEEK_FINALIZE|SEEK_PUSH_AUDIO) override
void setupProducer(boost::optional< QFileInfo > file)
Sets up an MLT::Producer object in response to audio being added to a Krita document or when canvas c...
void setDropFramesMode(bool value) override
void setAudioVolume(qreal volumeNormalized)
setAudioVolume
FrameWaitingInterface * frameWaitingInterface()
KisPlaybackEngineMLT(QObject *parent=nullptr)
void unsetCanvas() override
Krita's base animation playback engine for producing image frame changes and associated audio.
virtual void setDropFramesMode(bool value)
class KisCanvas2 * activeCanvas() const
virtual void setCanvas(KoCanvasBase *p_canvas) override
A simple wrapper class that hides boost includes from QtCreator preventing it from crashing when one ...
A simple wrapper class that hides boost includes from QtCreator preventing it from crashing when one ...
void reset(int windowSize)
static bool qFuzzyIsNull(half h)
#define KIS_ASSERT_RECOVER_RETURN_VALUE(cond, val)
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
typedef void(QOPENGLF_APIENTRYP PFNGLINVALIDATEBUFFERDATAPROC)(GLuint buffer)
QWaitCondition renderingWaitCondition
QMutex renderingControlMutex
void initializeConsumers()
QScopedPointer< Mlt::Event > pullConsumerConnection
KisPlaybackEngineMLT * m_self
QScopedPointer< KisSignalCompressorWithParam< double > > sigSetPlaybackSpeed
KisCanvas2 * activeCanvas()
PlaybackMode activePlaybackMode()
QScopedPointer< KisSignalCompressorWithParam< int > > sigPushAudioCompressor
FrameWaitingInterface frameWaitingInterface
QMap< KisCanvas2 *, QSharedPointer< Mlt::Producer > > canvasProducers
FrameRenderingStats frameStats
QScopedPointer< Mlt::PushConsumer > pushConsumer
QScopedPointer< Mlt::Profile > profile
Private(KisPlaybackEngineMLT *p_self)
QScopedPointer< Mlt::Consumer > pullConsumer
QSharedPointer< Mlt::Producer > activeProducer()
QScopedPointer< Mlt::Repository > repository
void pushAudio(int frame)
The StopAndResumeConsumer struct is used to encapsulate optional stop-and-then-resume behavior of a c...
StopAndResume(KisPlaybackEngineMLT::Private *p_d, bool requireFullRestart=false)
qreal droppedFramesPortion