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();
155 qDebug() <<
"Initializing MLT Animation Playback Engine. MLT ver: " << QString(mlt_version_get_string());
160#ifdef MLT_LOG_REDIRECTION
161 mlt_log_set_level(MLT_LOG_VERBOSE);
162 mlt_log_set_callback(&qt_redirection_callback);
168 profile.reset(
new Mlt::Profile());
169 profile->set_frame_rate(24, 1);
191 Mlt::Factory::close();
204 for (
int i = 0; i < SCRUB_AUDIO_WINDOW; i++ ) {
311 if (requireFullRestart) {
390 m_d->sigPushAudioCompressor->start(frameIndex);
407 if (file.has_value()) {
411 new Mlt::Producer(*
m_d->profile,
415 new Mlt::Producer(*
m_d->profile,
"krita_play_chunk", file->absoluteFilePath().toUtf8().data()));
417 if (producer->is_valid()) {
425 qDebug() <<
"Warning: Invalid MLT producer for file: " <<
ppVar(file->absoluteFilePath()) <<
" Falling back to audio-less playback.";
436 producer->set(
"limit_enabled",
false);
437 producer->set(
"speed",
m_d->playbackSpeed);
452 if (animationState) {
453 this->disconnect(animationState);
454 animationState->disconnect(
this);
459 if (image && image->animationInterface()) {
460 this->disconnect(image->animationInterface());
461 image->animationInterface()->disconnect(
this);
481 if (animationState) {
487 m_d->sigSetPlaybackSpeed->start(
value);
498 StopAndResume callbackStopResume(m_d.data());
499 m_d->profile->set_frame_rate(activeCanvas()->image()->animationInterface()->framerate(), 1);
509 KisCanvasAnimationState* animationState = activeCanvas()->animationState();
510 if (animationState) {
511 setupProducer(animationState->mediaInfo());
516 m_d->profile->set_frame_rate(
activeCanvas()->image()->animationInterface()->framerate(), 1);
519 QSharedPointer<Mlt::Producer> producer = m_d->canvasProducers[activeCanvas()];
520 auto image = activeCanvas()->image();
521 KIS_SAFE_ASSERT_RECOVER_RETURN(image);
522 producer->set(
"start_frame", image->animationInterface()->activePlaybackRange().start());
523 producer->set(
"end_frame", image->animationInterface()->activePlaybackRange().end());
543 for (
auto it =
m_d->canvasProducers.begin(); it !=
m_d->canvasProducers.end(); ++it) {
544 if (it.key() == canvas) {
545 m_d->canvasProducers.erase(it);
556 if (
m_d->frameStats.lastRenderedFrame < 0) {
557 m_d->frameStats.timeSinceLastFrame.start();
559 const int droppedFrames = qMax(0, frame -
m_d->frameStats.lastRenderedFrame - 1);
560 m_d->frameStats.averageFrameDuration(
m_d->frameStats.timeSinceLastFrame.restart());
561 m_d->frameStats.droppedFramesCount(droppedFrames);
563 m_d->frameStats.lastRenderedFrame = frame;
569 QMutexLocker l(&
m_d->frameWaitingInterface.renderingControlMutex);
570 m_d->frameWaitingInterface.waitingForFrame =
false;
571 m_d->frameWaitingInterface.renderingWaitCondition.wakeAll();
578 m_d->playbackSpeed = speed;
584 m_d->pullConsumer->set(
"volume", 0.0);
585 m_d->pushConsumer->set(
"volume", 0.0);
587 m_d->pullConsumer->set(
"volume", volumeNormalized);
588 m_d->pushConsumer->set(
"volume", volumeNormalized);
594 return &
m_d->frameWaitingInterface;
627 const int droppedFrames =
m_d->frameStats.droppedFramesCount.rollingSum();
628 const int totalFrames =
629 m_d->frameStats.droppedFramesCount.rollingCount() +
635 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
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