From ac153e4cff49a669e1908a88a9e7a64eefd7d169 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 1 Oct 2018 14:51:36 +1000 Subject: [PATCH] extremely limited speed support implemented #120 --- dialogs/speeddialog.cpp | 95 +++++++++++++++++++++++++++++++++++------ dialogs/speeddialog.h | 10 +++++ io/media.cpp | 9 ---- io/media.h | 2 - io/previewgenerator.cpp | 2 +- mainwindow.cpp | 2 +- panels/project.cpp | 48 +++------------------ playback/cacher.cpp | 37 +++++++++------- playback/playback.cpp | 7 ++- project/clip.cpp | 1 + project/clip.h | 1 + ui/labelslider.cpp | 5 ++- ui/labelslider.h | 1 + ui/timelinewidget.cpp | 14 ++++-- 14 files changed, 143 insertions(+), 91 deletions(-) diff --git a/dialogs/speeddialog.cpp b/dialogs/speeddialog.cpp index fd3fa75bb..bb2de5ffe 100644 --- a/dialogs/speeddialog.cpp +++ b/dialogs/speeddialog.cpp @@ -6,36 +6,103 @@ #include #include #include +#include #include "ui/labelslider.h" +#include "project/clip.h" +#include "project/sequence.h" +#include "io/media.h" +#include "playback/playback.h" SpeedDialog::SpeedDialog(QWidget *parent) : QDialog(parent) { - QGridLayout* layout = new QGridLayout(); - layout->setSpacing(6); - layout->setMargin(9); + QVBoxLayout* main_layout = new QVBoxLayout(); + setLayout(main_layout); - setLayout(layout); + QGridLayout* grid = new QGridLayout(); + grid->setSpacing(6); - layout->addWidget(new QLabel("Speed:"), 0, 0); + grid->addWidget(new QLabel("Speed:"), 0, 0); percent = new LabelSlider(); + percent->decimal_places = 2; percent->set_display_type(LABELSLIDER_PERCENT); - layout->addWidget(percent, 0, 1); + grid->addWidget(percent, 0, 1); - layout->addWidget(new QLabel("Frame Rate:"), 1, 0); + grid->addWidget(new QLabel("Frame Rate:"), 1, 0); frame_rate = new LabelSlider(); - layout->addWidget(frame_rate, 1, 1); + frame_rate->decimal_places = 3; + grid->addWidget(frame_rate, 1, 1); - layout->addWidget(new QLabel("Duration:"), 2, 0); + grid->addWidget(new QLabel("Duration:"), 2, 0); duration = new LabelSlider(); duration->set_display_type(LABELSLIDER_FRAMENUMBER); - layout->addWidget(duration, 2, 1); + grid->addWidget(duration, 2, 1); + + main_layout->addLayout(grid); - QHBoxLayout* reverseLayout = new QHBoxLayout(); reverse = new QCheckBox("Reverse"); - reverseLayout->addWidget(reverse); - layout->addLayout(reverseLayout, 3, 0, 2, 1); + main_layout->addWidget(reverse); + main_layout->addWidget(new QCheckBox("Maintain Audio Pitch")); + main_layout->addWidget(new QCheckBox("Ripple")); QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - layout->addWidget(buttonBox, 4, 0, 2, 1); + buttonBox->setCenterButtons(true); + main_layout->addWidget(buttonBox); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); + + connect(percent, SIGNAL(valueChanged()), this, SLOT(percent_update())); + connect(frame_rate, SIGNAL(valueChanged()), this, SLOT(frame_rate_update())); + connect(duration, SIGNAL(valueChanged()), this, SLOT(duration_update())); +} + +void SpeedDialog::run() { + if (clips.size() > 1) { + qDebug() << "[WARNING] Setting multiple clip speeds at once is unsupported and may break things - sorry!"; + } + + normal_fr = 1; + + Clip* first_clip = clips.at(0); + + switch (first_clip->media_type) { + case MEDIA_TYPE_FOOTAGE: + normal_fr = static_cast(first_clip->media)->get_stream_from_file_index(true, first_clip->media_stream)->video_frame_rate; + break; + case MEDIA_TYPE_SEQUENCE: + normal_fr = static_cast(first_clip->media)->frame_rate; + break; + } + + frame_rate->set_default_value(first_clip->frame_rate); + percent->set_default_value((first_clip->frame_rate / normal_fr) * 100); + duration->set_default_value(first_clip->getLength()); + + exec(); +} + +void SpeedDialog::percent_update() { + double pc = (percent->value()*0.01); + frame_rate->set_value(normal_fr * pc, false); + duration->set_value(clips.at(0)->getLength() / pc, false); +} + +void SpeedDialog::duration_update() { + double pc = clips.at(0)->getLength() / duration->value(); + frame_rate->set_value(normal_fr * pc, false); + percent->set_value(pc * 100, false); +} + +void SpeedDialog::frame_rate_update() { + double fr = (frame_rate->value()); + double pc = (fr / normal_fr); + percent->set_value(pc * 100, false); + duration->set_value(clips.at(0)->getLength() / pc, false); +} + +void SpeedDialog::accept() { + for (int i=0;iframe_rate = frame_rate->value(); + close_clip(clips.at(i)); + } + QDialog::accept(); } diff --git a/dialogs/speeddialog.h b/dialogs/speeddialog.h index efd386403..c6f1e51bc 100644 --- a/dialogs/speeddialog.h +++ b/dialogs/speeddialog.h @@ -9,15 +9,25 @@ class QCheckBox; class SpeedDialog : public QDialog { + Q_OBJECT public: SpeedDialog(QWidget* parent = 0); QVector clips; + + void run(); +private slots: + void percent_update(); + void duration_update(); + void frame_rate_update(); + void accept(); private: LabelSlider* percent; LabelSlider* duration; LabelSlider* frame_rate; QCheckBox* reverse; + + double normal_fr; }; #endif // SPEEDDIALOG_H diff --git a/io/media.cpp b/io/media.cpp index 0d1876197..d3080d911 100644 --- a/io/media.cpp +++ b/io/media.cpp @@ -46,12 +46,3 @@ MediaStream* Media::get_stream_from_file_index(bool video, int index) { } return NULL; } - -int guess_layout_from_channels(int channel_count) { - if (channel_count == 1) { - return AV_CH_LAYOUT_MONO; - } else { - qDebug() << "[WARNING] Could not detect audio channel layout - assuming stereo"; - return AV_CH_LAYOUT_STEREO; - } -} diff --git a/io/media.h b/io/media.h index 6a13b5d89..92b678aba 100644 --- a/io/media.h +++ b/io/media.h @@ -50,6 +50,4 @@ struct Media { void reset(); }; -int guess_layout_from_channels(int channel_count); - #endif // MEDIA_H diff --git a/io/previewgenerator.cpp b/io/previewgenerator.cpp index 8ef3e4148..c9206323c 100644 --- a/io/previewgenerator.cpp +++ b/io/previewgenerator.cpp @@ -110,7 +110,7 @@ void PreviewGenerator::generate_waveform() { avcodec_open2(codec_ctx[i], codec, NULL); if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && codec_ctx[i]->channel_layout == 0) { - codec_ctx[i]->channel_layout = guess_layout_from_channels(fmt_ctx->streams[i]->codecpar->channels); + codec_ctx[i]->channel_layout = av_get_default_channel_layout(fmt_ctx->streams[i]->codecpar->channels); } } } diff --git a/mainwindow.cpp b/mainwindow.cpp index 6e55864f1..12c735858 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -300,7 +300,7 @@ void MainWindow::openSpeedDialog() { s.clips.append(c); } } - if (s.clips.size() > 0) s.exec(); + if (s.clips.size() > 0) s.run(); } } diff --git a/panels/project.cpp b/panels/project.cpp index 9592f6544..eb89f18ce 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -685,46 +685,7 @@ bool Project::load_worker(QFile& f, QXmlStreamReader& stream, int type) { } else if (attr.name() == "duration") { m->length = attr.value().toLongLong(); } - } - - /*while (!(stream.name() == child_search && stream.isEndElement())) { - stream.readNext(); - if (stream.isStartElement()) { - if (stream.name() == "video") { - MediaStream* ms = new MediaStream(); - for (int j=0;jfile_index = attr.value().toInt(); - } else if (attr.name() == "width") { - ms->video_width = attr.value().toInt(); - } else if (attr.name() == "height") { - ms->video_height = attr.value().toInt(); - } else if (attr.name() == "framerate") { - ms->video_frame_rate = attr.value().toDouble(); - } else if (attr.name() == "infinite") { - ms->infinite_length = (attr.value().toInt() == 1); - } - } - m->video_tracks.append(ms); - } else if (stream.name() == "audio") { - MediaStream* ms = new MediaStream(); - for (int j=0;jfile_index = attr.value().toInt(); - } else if (attr.name() == "channels") { - ms->audio_channels = attr.value().toInt(); - } else if (attr.name() == "layout") { - ms->audio_layout = attr.value().toInt(); - } else if (attr.name() == "frequency") { - ms->audio_frequency = attr.value().toInt(); - } - } - m->audio_tracks.append(ms); - } - } - }*/ + } set_footage_of_tree(item, m); @@ -794,7 +755,8 @@ bool Project::load_worker(QFile& f, QXmlStreamReader& stream, int type) { Clip* c = new Clip(s); // backwards compatibility code - c->autoscale = false; + c->autoscale = false; + c->frame_rate = 0; c->media = NULL; for (int j=0;jframe_rate = attr.value().toDouble(); } else if (attr.name() == "sequence") { c->media_type = MEDIA_TYPE_SEQUENCE; @@ -1125,6 +1089,8 @@ void Project::save_folder(QXmlStreamWriter& stream, QTreeWidgetItem* parent, int stream.writeAttribute("autoscale", QString::number(c->autoscale)); + stream.writeAttribute("framerate", QString::number(c->frame_rate)); + stream.writeAttribute("type", QString::number(c->media_type)); switch (c->media_type) { case MEDIA_TYPE_FOOTAGE: diff --git a/playback/cacher.cpp b/playback/cacher.cpp index 2ffb12e27..e0f4b9f07 100644 --- a/playback/cacher.cpp +++ b/playback/cacher.cpp @@ -170,7 +170,6 @@ void cache_audio_worker(Clip* c, Clip* nest) { } else { long buffer_timeline_out = get_buffer_offset_from_frame(timeline_out); audio_write_lock.lock(); - qDebug() << "starting to write:" << audio_ibuffer_read; while (c->frame_sample_index < nb_bytes && c->audio_buffer_write < audio_ibuffer_read+audio_ibuffer_size && c->audio_buffer_write < buffer_timeline_out) { @@ -207,6 +206,8 @@ void cache_video_worker(Clip* c, long playhead, ClipCache* cache) { int i = 0; + double fr_ratio = qRound((c->sequence->frame_rate / c->frame_rate)*100)*0.01; + /* old swscale solution if (!c->reached_end) { while (i < c->cache_size) { @@ -227,11 +228,19 @@ void cache_video_worker(Clip* c, long playhead, ClipCache* cache) { if (ret < 0) { if (ret == AVERROR(EAGAIN)) { ret = retrieve_next_frame(c, c->frame); + if (ret >= 0) { - if ((ret = av_buffersrc_add_frame_flags(c->buffersrc_ctx, c->frame, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0) { - qDebug() << "[ERROR] Could not feed filtergraph -" << ret; - error = true; - break; + // optimization: mathematically determine based on the sequence and clips' frame rates whether this frame will actually be shown + double timeline_frame = (i+cache->offset) * fr_ratio; + + if (qFloor(timeline_frame) == timeline_frame) { + if ((ret = av_buffersrc_add_frame_flags(c->buffersrc_ctx, c->frame, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0) { + qDebug() << "[ERROR] Could not feed filtergraph -" << ret; + error = true; + break; + } + } else { + i++; } } else { if (ret == AVERROR_EOF) { @@ -442,29 +451,25 @@ void open_clip_worker(Clip* clip) { clip->stream->codecpar->sample_aspect_ratio.den ); - char errormsg[100]; + avfilter_graph_create_filter(&clip->buffersrc_ctx, avfilter_get_by_name("buffer"), "in", args, NULL, clip->filter_graph); - AVFilter* buffersrc = avfilter_get_by_name("buffer"); - AVFilter* buffersink = avfilter_get_by_name("buffersink"); + AVFilterContext* negate_ctx; + avfilter_graph_create_filter(&negate_ctx, avfilter_get_by_name("setpts"), "rev", "0.5*PTS", NULL, clip->filter_graph); - if (buffersrc == NULL || buffersink == NULL) { - qDebug() << "lol"; - } - - avfilter_graph_create_filter(&clip->buffersrc_ctx, buffersrc, "in", args, NULL, clip->filter_graph); - avfilter_graph_create_filter(&clip->buffersink_ctx, buffersink, "out", NULL, NULL, clip->filter_graph); + avfilter_graph_create_filter(&clip->buffersink_ctx, avfilter_get_by_name("buffersink"), "out", NULL, NULL, clip->filter_graph); enum AVPixelFormat pix_fmts[] = { static_cast(dest_format), AV_PIX_FMT_NONE }; av_opt_set_int_list(clip->buffersink_ctx, "pix_fmts", pix_fmts, AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN); - avfilter_link(clip->buffersrc_ctx, 0, clip->buffersink_ctx, 0); + avfilter_link(clip->buffersrc_ctx, 0, negate_ctx, 0); + avfilter_link(negate_ctx, 0, clip->buffersink_ctx, 0); avfilter_graph_config(clip->filter_graph, NULL); } else if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { // if FFmpeg can't pick up the channel layout (usually WAV), assume // based on channel count (doesn't support surround sound sources yet) if (clip->codecCtx->channel_layout == 0) { - clip->codecCtx->channel_layout = guess_layout_from_channels(clip->stream->codecpar->channels); + clip->codecCtx->channel_layout = av_get_default_channel_layout(clip->stream->codecpar->channels); } // init resampling context diff --git a/playback/playback.cpp b/playback/playback.cpp index 0e3f56a4a..cd8cb1051 100644 --- a/playback/playback.cpp +++ b/playback/playback.cpp @@ -114,8 +114,13 @@ bool get_clip_frame(Clip* c, long playhead) { // do we need to update the texture? MediaStream* ms = static_cast(c->media)->get_stream_from_file_index(c->track < 0, c->media_stream); + // backwards compatibilty code + if (c->frame_rate == 0) { + c->frame_rate = ms->video_frame_rate; + } + long sequence_clip_time = playhead - c->timeline_in + c->clip_in; - long clip_time = refactor_frame_number(sequence_clip_time, c->sequence->frame_rate, ms->video_frame_rate); + long clip_time = refactor_frame_number(sequence_clip_time, c->sequence->frame_rate, c->frame_rate); AVFrame* current_frame = NULL; bool no_frame = false; diff --git a/project/clip.cpp b/project/clip.cpp index ccbcf9501..f67679c8f 100644 --- a/project/clip.cpp +++ b/project/clip.cpp @@ -54,6 +54,7 @@ Clip* Clip::copy(Sequence* s) { copy->media_type = media_type; copy->media_stream = media_stream; copy->autoscale = autoscale; + copy->frame_rate = frame_rate; for (int i=0;ieffects.append(effects.at(i)->copy(copy)); diff --git a/project/clip.h b/project/clip.h index eb5545b49..5e1f2407b 100644 --- a/project/clip.h +++ b/project/clip.h @@ -69,6 +69,7 @@ struct Clip int media_stream; int getWidth(); int getHeight(); + double frame_rate; // other variables (should be "duplicated" in copy()) QList effects; diff --git a/ui/labelslider.cpp b/ui/labelslider.cpp index 46f30c71d..1a5316ab3 100644 --- a/ui/labelslider.cpp +++ b/ui/labelslider.cpp @@ -21,6 +21,7 @@ LabelSlider::LabelSlider(QWidget* parent) : QLabel(parent) { set_value(0, false); set = false; display_type = LABELSLIDER_NORMAL; + decimal_places = 1; } void LabelSlider::set_display_type(int type) { @@ -54,9 +55,9 @@ bool LabelSlider::is_dragging() { QString LabelSlider::valueToString(double v) { switch (display_type) { case LABELSLIDER_FRAMENUMBER: return frame_to_timecode(v, config.timecode_view, sequence->frame_rate); - case LABELSLIDER_PERCENT: return QString::number(v, 'f', 1) + "%"; + case LABELSLIDER_PERCENT: return QString::number(v, 'f', decimal_places) + "%"; } - return QString::number(v, 'f', 1); + return QString::number(v, 'f', decimal_places); } double LabelSlider::getPreviousValue() { diff --git a/ui/labelslider.h b/ui/labelslider.h index 85ed309e5..45fdd13a5 100644 --- a/ui/labelslider.h +++ b/ui/labelslider.h @@ -24,6 +24,7 @@ public: bool is_dragging(); virtual QString valueToString(double v); double getPreviousValue(); + int decimal_places; protected: void mousePressEvent(QMouseEvent *ev); void mouseMoveEvent(QMouseEvent *ev); diff --git a/ui/timelinewidget.cpp b/ui/timelinewidget.cpp index 417da5b04..3ff64b526 100644 --- a/ui/timelinewidget.cpp +++ b/ui/timelinewidget.cpp @@ -329,6 +329,7 @@ void TimelineWidget::dropEvent(QDropEvent* event) { c->timeline_in = g.in; c->timeline_out = g.out; c->clip_in = g.clip_in; + c->track = g.track; if (c->media_type == MEDIA_TYPE_FOOTAGE) { Media* m = static_cast(c->media); if (m->video_tracks.size() == 0) { @@ -347,6 +348,9 @@ void TimelineWidget::dropEvent(QDropEvent* event) { c->color_g = 128; c->color_b = 192; } + if (c->track < 0) { + c->frame_rate = m->get_stream_from_file_index(true, c->media_stream)->video_frame_rate; + } c->name = m->name; } else if (c->media_type == MEDIA_TYPE_SEQUENCE) { // sequence (red?ish?) @@ -354,11 +358,11 @@ void TimelineWidget::dropEvent(QDropEvent* event) { c->color_g = 128; c->color_b = 128; - c->name = static_cast(c->media)->name; + Sequence* media = static_cast(c->media); + c->frame_rate = media->frame_rate; + c->name = media->name; } - c->track = g.track; - added_clips.append(c); } @@ -617,11 +621,13 @@ void TimelineWidget::mouseReleaseEvent(QMouseEvent *event) { c->color_b = 64; c->track = g.track; + if (c->track < 0) c->frame_rate = sequence->frame_rate; + QVector add; add.append(c); ca->append(new AddClipCommand(sequence, add)); - int clipIndex = sequence->clips.size(); +// int clipIndex = sequence->clips.size(); if (c->track < 0) { // default video effects (before custom effects) c->effects.append(create_effect(VIDEO_TRANSFORM_EFFECT, c));