From 386693d372916cf56a81e5d8dd8417e35c4e0098 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sat, 20 Oct 2018 22:58:19 +1100 Subject: [PATCH] many improvements to the playback algorithm --- dialogs/exportdialog.cpp | 6 +- io/exportthread.cpp | 3 +- io/media.cpp | 3 +- io/previewgenerator.cpp | 4 +- panels/project.cpp | 20 ++-- playback/cacher.cpp | 79 ++++++++------ playback/playback.cpp | 188 +++++++++------------------------ project/clip.cpp | 4 +- project/clip.h | 2 +- ui/timelinewidget.cpp | 4 +- ui/viewerwidget.cpp | 220 +++++++++++++++++++-------------------- 11 files changed, 234 insertions(+), 299 deletions(-) diff --git a/dialogs/exportdialog.cpp b/dialogs/exportdialog.cpp index f18dcfaeb..0f7083b4d 100644 --- a/dialogs/exportdialog.cpp +++ b/dialogs/exportdialog.cpp @@ -52,7 +52,10 @@ ExportDialog::ExportDialog(QWidget *parent) : ui->setupUi(this); ui->rangeCombobox->setCurrentIndex(0); - ui->rangeCombobox->setEnabled(sequence->using_workarea); + if (sequence->using_workarea) { + ui->rangeCombobox->setEnabled(sequence->using_workarea); + ui->rangeCombobox->setCurrentIndex(1); + } format_strings.resize(FORMAT_SIZE); format_strings[FORMAT_3GPP] = "3GPP"; @@ -471,6 +474,7 @@ void ExportDialog::on_pushButton_clicked() { closeActiveClips(sequence, true); + rendering = true; panel_sequence_viewer->viewer_widget->context()->doneCurrent(); panel_sequence_viewer->viewer_widget->context()->moveToThread(et); diff --git a/io/exportthread.cpp b/io/exportthread.cpp index 71e4485a8..3692c18d6 100644 --- a/io/exportthread.cpp +++ b/io/exportthread.cpp @@ -341,7 +341,6 @@ void ExportThread::run() { QOpenGLFramebufferObject fbo(sequence->width, sequence->height, QOpenGLFramebufferObject::CombinedDepthStencil, GL_TEXTURE_RECTANGLE); fbo.bind(); - rendering = true; panel_sequence_viewer->viewer_widget->default_fbo = &fbo; long file_audio_samples = 0; @@ -458,4 +457,6 @@ void ExportThread::run() { panel_sequence_viewer->viewer_widget->context()->doneCurrent(); panel_sequence_viewer->viewer_widget->context()->moveToThread(qApp->thread()); + + rendering = false; } diff --git a/io/media.cpp b/io/media.cpp index 4e0d17767..7947c5667 100644 --- a/io/media.cpp +++ b/io/media.cpp @@ -1,6 +1,7 @@ #include "media.h" #include +#include #include "io/previewgenerator.h" extern "C" { @@ -32,7 +33,7 @@ void Media::reset() { } long Media::get_length_in_frames(double frame_rate) { - return ceil(((double) length / (double) AV_TIME_BASE) * frame_rate); + return qFloor(((double) length / (double) AV_TIME_BASE) * frame_rate); } MediaStream* Media::get_stream_from_file_index(bool video, int index) { diff --git a/io/previewgenerator.cpp b/io/previewgenerator.cpp index 0d6e8f8b6..f8122d64a 100644 --- a/io/previewgenerator.cpp +++ b/io/previewgenerator.cpp @@ -74,7 +74,7 @@ void PreviewGenerator::parse_media() { } } } - media->length = fmt_ctx->duration; + media->length = fmt_ctx->duration; if (fmt_ctx->duration == INT64_MIN) { retrieve_duration = true; @@ -284,7 +284,7 @@ void PreviewGenerator::generate_waveform() { maximum_stream = i; } } - media->length = (double) media_lengths[maximum_stream] / av_q2d(fmt_ctx->streams[maximum_stream]->avg_frame_rate) * AV_TIME_BASE; + media->length = (double) media_lengths[maximum_stream] / av_q2d(fmt_ctx->streams[maximum_stream]->avg_frame_rate) * AV_TIME_BASE; // TODO redo with PTS finalize_media(); } delete [] media_lengths; diff --git a/panels/project.cpp b/panels/project.cpp index 3b7e0113d..fdc45d4d0 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -789,8 +789,9 @@ bool Project::load_worker(QFile& f, QXmlStreamReader& stream, int type) { s->audio_layout = attr.value().toInt(); } else if (attr.name() == "open") { open_seq = s; - } else if (attr.name() == "workareaIn") { - s->using_workarea = true; + } else if (attr.name() == "workarea") { + s->using_workarea = (attr.value() == "1"); + } else if (attr.name() == "workareaIn") { s->workarea_in = attr.value().toLong(); } else if (attr.name() == "workareaOut") { s->workarea_out = attr.value().toLong(); @@ -1129,11 +1130,10 @@ void Project::save_folder(QXmlStreamWriter& stream, QTreeWidgetItem* parent, int stream.writeAttribute("alayout", QString::number(s->audio_layout)); if (s == sequence) { stream.writeAttribute("open", "1"); - } - if (s->using_workarea) { - stream.writeAttribute("workareaIn", QString::number(s->workarea_in)); - stream.writeAttribute("workareaOut", QString::number(s->workarea_out)); - } + } + stream.writeAttribute("workarea", QString::number(s->using_workarea)); + stream.writeAttribute("workareaIn", QString::number(s->workarea_in)); + stream.writeAttribute("workareaOut", QString::number(s->workarea_out)); for (int j=0;jclips.size();j++) { Clip* c = s->clips.at(j); if (c != NULL) { @@ -1390,9 +1390,11 @@ void update_footage_tooltip(QTreeWidgetItem *item, Media *media, QString error) if (i > 0) { tooltip += ", "; } - tooltip += QString::number(media->video_tracks.at(i)->video_frame_rate); if (media->video_tracks.at(i)->video_interlacing != VIDEO_PROGRESSIVE) { - tooltip += " fields (" + QString::number(media->video_tracks.at(i)->video_frame_rate*0.5) + " frames)"; + tooltip += QString::number(media->video_tracks.at(i)->video_frame_rate); + } else { + tooltip += QString::number(media->video_tracks.at(i)->video_frame_rate * 2); + tooltip += " fields (" + QString::number(media->video_tracks.at(i)->video_frame_rate) + " frames)"; } } tooltip += "\n"; diff --git a/playback/cacher.cpp b/playback/cacher.cpp index 5b50f4891..bdf02059c 100644 --- a/playback/cacher.cpp +++ b/playback/cacher.cpp @@ -98,6 +98,7 @@ void cache_audio_worker(Clip* c, Clip* nest) { if (c->reverse && !c->audio_just_reset) { avcodec_flush_buffers(c->codecCtx); + c->reached_end = false; int64_t backtrack_seek = qMax(c->reverse_target - static_cast(av_q2d(av_inv_q(c->stream->time_base))), static_cast(0)); av_seek_frame(c->formatCtx, c->stream->index, backtrack_seek, AVSEEK_FLAG_BACKWARD); #ifdef AUDIOWARNINGS @@ -248,7 +249,7 @@ void cache_audio_worker(Clip* c, Clip* nest) { if (c->audio_just_reset) { // get precise sample offset for the elected clip_in from this audio frame double target_sts = playhead_to_clip_seconds(c, c->audio_target_frame); - double frame_sts = (frame->pts * timebase); + double frame_sts = ((frame->pts - c->stream->start_time) * timebase); int nb_samples = qRound((target_sts - frame_sts)*c->sequence->audio_frequency); c->frame_sample_index = nb_samples * 4; #ifdef AUDIOWARNINGS @@ -361,16 +362,20 @@ void cache_video_worker(Clip* c, long playhead) { if (c->reverse) limit *= 2; if (c->queue.size() < limit) { + int64_t eighth_second = av_q2d(av_inv_q(c->stream->time_base))*0.125; int64_t smallest_pts = INT64_MAX; + int64_t target_pts = seconds_to_timestamp(c, playhead_to_clip_seconds(c, playhead)); if (c->reverse && c->queue.size() > 0) { int64_t quarter_sec = qRound(av_q2d(av_inv_q(c->stream->time_base))) >> 2; for (int i=0;iqueue.size();i++) { smallest_pts = qMin(smallest_pts, c->queue.at(i)->pts); } avcodec_flush_buffers(c->codecCtx); - av_seek_frame(c->formatCtx, c->stream->index, qMax(static_cast(0), smallest_pts - quarter_sec), AVSEEK_FLAG_BACKWARD); + c->reached_end = false; + int64_t seek_ts = qMax(static_cast(0), smallest_pts - quarter_sec); + av_seek_frame(c->formatCtx, c->stream->index, seek_ts, AVSEEK_FLAG_BACKWARD); } else { - smallest_pts = seconds_to_timestamp(c, playhead_to_clip_seconds(c, playhead)); + smallest_pts = target_pts; } while (true) { @@ -381,58 +386,67 @@ void cache_video_worker(Clip* c, long playhead) { read_ret = (c->use_existing_frame) ? 0 : retrieve_next_frame(c, send_frame); c->use_existing_frame = false; if (read_ret >= 0) { - // sending NULL to avfiltergraph is supposed to flush it but it'll also no longer accept frames - particularly unhelpful when reversing - // we'll see if it still works completely without doing so - /*if (read_ret == AVERROR_EOF) send_frame = NULL; - if ((send_ret = av_buffersrc_add_frame_flags(c->buffersrc_ctx, send_frame, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0) { - qDebug() << "[ERROR] Failed to add frame to buffer source." << send_ret; - break; - } - if (read_ret >= 0) { - av_frame_unref(c->frame); - }*/ + bool send_it = false; - if ((send_ret = av_buffersrc_add_frame_flags(c->buffersrc_ctx, send_frame, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0) { - qDebug() << "[ERROR] Failed to add frame to buffer source." << send_ret; - break; + if (c->reverse) { + send_it = true; + } else if (send_frame->pts > target_pts - eighth_second) { + send_it = true; + } else { + qDebug() << "skipped adding a frame to the queue" << target_pts << "(" << send_frame->pts << send_frame->pkt_duration; } + + if (send_it) { + if ((send_ret = av_buffersrc_add_frame_flags(c->buffersrc_ctx, send_frame, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0) { + qDebug() << "[ERROR] Failed to add frame to buffer source." << send_ret; + break; + } + } + av_frame_unref(c->frame); } else { - if (read_ret != AVERROR_EOF) qDebug() << "[ERROR] Failed to read frame." << read_ret; + if (read_ret == AVERROR_EOF) { + c->reached_end = true; + } else { + qDebug() << "[ERROR] Failed to read frame." << read_ret; + } break; } } if (retr_ret < 0) { - if (retr_ret != AVERROR_EOF) qDebug() << "[ERROR] Failed to retrieve frame from buffersink." << retr_ret; + if (retr_ret == AVERROR_EOF) { + c->reached_end = true; + } else { + qDebug() << "[ERROR] Failed to retrieve frame from buffersink." << retr_ret; + } av_frame_free(&frame); break; } else { - if (c->reverse && frame->pts >= smallest_pts) { + if (c->reverse && ((smallest_pts == target_pts && frame->pts >= smallest_pts) || (smallest_pts != target_pts && frame->pts > smallest_pts))) { av_frame_free(&frame); break; } else { // thread-safety while adding frame to the queue c->queue_lock.lock(); c->queue.append(frame); - c->queue_lock.unlock(); - //break; if (!c->reverse && c->queue.size() == limit) { - if (rendering) { +// if (rendering) { // see if we got the frame we needed (used for speed ups primarily) bool found = false; for (int i=0;iqueue.size();i++) { - // TODO/NOTE: this will not work on sped up AND reversed video - if (c->queue.at(i)->pts >= smallest_pts) { + // TODO/NOTE: this will not work on clips that are sped up AND reversed + if (c->queue.at(i)->pts >= target_pts) { found = true; break; } } if (found) { + c->queue_lock.unlock(); break; } else { - // remove earliest frame and store another + // remove earliest frame and loop to store another int earliest_frame = 0; for (int i=1;iqueue.size();i++) { // TODO/NOTE: this will not work on sped up AND reversed video @@ -440,15 +454,14 @@ void cache_video_worker(Clip* c, long playhead) { earliest_frame = i; } } - c->queue_lock.lock(); av_frame_free(&c->queue[earliest_frame]); c->queue.removeAt(earliest_frame); - c->queue_lock.unlock(); } - } else { - break; - } +// } else { +// break; +// } } + c->queue_lock.unlock(); } } } @@ -460,7 +473,6 @@ void reset_cache(Clip* c, long target_frame) { switch (c->media_type) { case MEDIA_TYPE_FOOTAGE: { - c->reached_end = false; MediaStream* ms = static_cast(c->media)->get_stream_from_file_index(c->track < 0, c->media_stream); if (!ms->infinite_length) { if (c->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { @@ -476,6 +488,7 @@ void reset_cache(Clip* c, long target_frame) { while (true) { // flush ffmpeg codecs avcodec_flush_buffers(c->codecCtx); + c->reached_end = false; if (seek_ts > 0) { av_seek_frame(c->formatCtx, ms->file_index, seek_ts, AVSEEK_FLAG_BACKWARD); @@ -502,6 +515,7 @@ void reset_cache(Clip* c, long target_frame) { } else if (c->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { // flush ffmpeg codecs avcodec_flush_buffers(c->codecCtx); + c->reached_end = false; // seek (target_frame represents timeline timecode in frames, not clip timecode) @@ -571,7 +585,8 @@ void open_clip_worker(Clip* clip) { clip->codecCtx = avcodec_alloc_context3(clip->codec); avcodec_parameters_to_context(clip->codecCtx, clip->stream->codecpar); - clip->max_queue_size = (ms->infinite_length) ? 1 : 15; + clip->max_queue_size = (ms->infinite_length) ? 1 : qCeil(ms->video_frame_rate*0.25); + if (ms->video_interlacing != VIDEO_PROGRESSIVE) clip->max_queue_size *= 2; AVDictionary* opts = NULL; diff --git a/playback/playback.cpp b/playback/playback.cpp index 414350ad0..2d3152eee 100644 --- a/playback/playback.cpp +++ b/playback/playback.cpp @@ -25,6 +25,10 @@ extern "C" { #include #include +#ifdef QT_DEBUG +//#define GCF_DEBUG +#endif + bool texture_failed = false; bool rendering = false; @@ -57,6 +61,7 @@ void open_clip(Clip* clip, bool multithreaded) { void close_clip(Clip* clip) { // destroy opengl texture in main thread if (clip->texture != NULL) { + clip->texture->destroy(); delete clip->texture; clip->texture = NULL; } @@ -117,17 +122,26 @@ void get_clip_frame(Clip* c, long playhead) { AVFrame* target_frame = NULL; bool reset = false; + bool cache = true; + c->queue_lock.lock(); if (c->queue.size() > 0) { if (ms->infinite_length) { target_frame = c->queue.at(0); +#ifdef GCF_DEBUG qDebug() << "GCF ==> USE PRECISE (INFINITE)"; +#endif } else { // correct frame may be somewhere else in the queue int closest_frame = 0; + for (int i=1;iqueue.size();i++) { + //qDebug() << "results for" << i << qAbs(c->queue.at(i)->pts - target_pts) << qAbs(c->queue.at(closest_frame)->pts - target_pts) << c->queue.at(i)->pts << target_pts; + if (c->queue.at(i)->pts == target_pts) { +#ifdef GCF_DEBUG qDebug() << "GCF ==> USE PRECISE"; +#endif closest_frame = i; break; } else if (c->queue.at(i)->pts > c->queue.at(closest_frame)->pts && c->queue.at(i)->pts < target_pts) { @@ -137,24 +151,44 @@ void get_clip_frame(Clip* c, long playhead) { // remove all frames earlier than this one from the queue target_frame = c->queue.at(closest_frame); - c->queue_lock.lock(); + //qDebug() << "closest frame was" << closest_frame << "with" << target_frame->pts << "/" << target_pts; for (int i=0;iqueue.size();i++) { - if (c->queue.at(i)->pts != target_frame->pts && (c->queue.at(i)->pts < target_frame->pts) == (!c->reverse)) { + if (c->queue.at(i) != target_frame && ((c->queue.at(i)->pts > target_frame->pts) == c->reverse)) { + //qDebug() << "removed frame at" << i << "because its pts was" << c->queue.at(i)->pts << "compared to" << target_frame->pts; av_frame_free(&c->queue[i]); // may be a little heavy for the UI thread? c->queue.removeAt(i); i--; } } - c->queue_lock.unlock(); - // if this frame is more than one second out, we probably need to reset - if (qAbs(target_pts - target_frame->pts) > second_pts) { - target_frame = NULL; - // qDebug() << "GCF ==> COMPLACENT"; - qDebug() << "GCF ==> RESET"; - reset = true; - } else { - qDebug() << "GCF ==> USE IMPRECISE"; + // we didn't get the exact frame + if (target_frame->pts != target_pts) { + if (target_pts > target_frame->pts && target_pts <= target_frame->pts + target_frame->pkt_duration) { +#ifdef GCF_DEBUG + qDebug() << "GCF ==> USE IMPRECISE"; +#endif + } else { + int64_t pts_diff = qAbs(target_pts - target_frame->pts); + if (c->reached_end && target_pts > target_frame->pts) { +#ifdef GCF_DEBUG + qDebug() << "GCF ==> EOF TOLERANT"; +#endif + c->reached_end = false; + cache = false; + } else if (target_pts < target_frame->pts || pts_diff > second_pts) { + +#ifdef GCF_DEBUG + qDebug() << "GCF ==> RESET" << target_pts << "(" << target_frame->pts << "-" << target_frame->pts+target_frame->pkt_duration << ")"; +#endif + target_frame = NULL; + reset = true; + } else { +#ifdef GCF_DEBUG + qDebug() << "GCF ==> WAIT - target:" << target_pts << "closest frame:" << target_frame->pts; +#endif + target_frame = NULL; + } + } } } } else { @@ -174,133 +208,13 @@ void get_clip_frame(Clip* c, long playhead) { glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); } + c->queue_lock.unlock(); + // get more frames - cache_clip(c, playhead, reset, NULL); + if (cache) cache_clip(c, playhead, reset, NULL); } } -/*bool get_clip_frame(Clip* c, long playhead) { - if (c->finished_opening) { - // do we need to update the texture? - MediaStream* ms = static_cast(c->media)->get_stream_from_file_index(c->track < 0, c->media_stream); - - long sequence_clip_time = qMax(0L, playhead - c->timeline_in + c->clip_in); - - if (c->reverse && !ms->infinite_length) { - sequence_clip_time = c->getMaximumLength() - sequence_clip_time - 1; - } - - double rate = c->getMediaFrameRate(); - if (c->skip_type == SKIP_TYPE_DISCARD) rate *= c->speed; - long clip_time = refactor_frame_number(sequence_clip_time, c->sequence->frame_rate, rate); - - AVFrame* current_frame = NULL; - bool no_frame = false; - - // get frame data - if (ms->infinite_length) { // if clip is a still frame, we only need one - if (c->cache_A.written) { - // retrieve cached frame - current_frame = c->cache_A.frames[0]; - } else if (c->lock.tryLock()) { - // grab image - cache_clip(c, 0, true, false, false, NULL); - c->lock.unlock(); - } - } else { - // keeping a RAM cache improves performance, however it's detrimental when rendering - // determine which cache contains the requested frame - bool using_cache_A = false; - bool using_cache_B = false; - AVFrame** cache = NULL; - long cache_offset = 0; - bool cache_needs_reset = false; - - // TODO just removed a bunch of mutexes - is this safe???? - if (c->cache_A.written && clip_time >= c->cache_A.offset && clip_time < c->cache_A.offset + c->cache_size) { - if (clip_time < (c->cache_A.offset + c->cache_A.write_count)) { - using_cache_A = true; - c->cache_A.unread = false; - cache = c->cache_A.frames; - cache_offset = c->cache_A.offset; - } else { - // frame is coming but isn't here yet, no need to reset cache - no_frame = true; - } - } else if (c->cache_B.written && clip_time >= c->cache_B.offset && clip_time < c->cache_B.offset + c->cache_size) { - if (clip_time < (c->cache_B.offset + c->cache_B.write_count)) { - using_cache_B = true; - c->cache_B.unread = false; - cache = c->cache_B.frames; - cache_offset = c->cache_B.offset; - } else { - // frame is coming but isn't here yet, no need to reset cache - no_frame = true; - } - } else { - // this is technically bad, unless we just seeked - c->cache_A.write_count = 0; - c->cache_B.write_count = 0; - c->cache_A.unread = false; - c->cache_B.unread = false; - cache_needs_reset = true; - } - - if (!no_frame) { - if (cache != NULL) { - current_frame = cache[clip_time - cache_offset]; - } - - // determine whether we should start filling the other cache - if (!using_cache_A || !using_cache_B) { - if (c->lock.tryLock()) { - long cache_time; - if (cache_needs_reset) { - cache_time = (c->reverse) ? qMax(clip_time - c->cache_size, 0L) : qMax(clip_time, 0L); - } else if (c->reverse) { - cache_time = (cache_offset - c->cache_size); - } else { - cache_time = (cache_offset + c->cache_size); - } - - bool write_A = (!using_cache_A && !c->cache_A.unread); - bool write_B = (!using_cache_B && !c->cache_B.unread); - if (write_A || write_B) { - // if we have no cache and need to seek, start us at the current playhead, otherwise start at the end of the current cache - cache_clip(c, cache_time, write_A, write_B, (cache_needs_reset || c->reverse), NULL); - } - c->lock.unlock(); - } - } - } - } - - if (playhead >= c->timeline_in) { - if (current_frame != NULL) { - // set up opengl texture - if (c->texture == NULL) { - c->texture = new QOpenGLTexture(QOpenGLTexture::Target2D); - c->texture->setSize(current_frame->width, current_frame->height); - c->texture->setFormat(QOpenGLTexture::RGBA8_UNorm); - c->texture->setMipLevels(c->texture->maximumMipLevels()); - c->texture->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear); - c->texture->allocateStorage(QOpenGLTexture::RGBA, QOpenGLTexture::UInt8); - } - - glPixelStorei(GL_UNPACK_ROW_LENGTH, current_frame->linesize[0]/4); - c->texture->setData(0, QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, current_frame->data[0]); - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - c->texture_frame = clip_time; - return true; - } else { - texture_failed = true; - qDebug() << "[ERROR] Failed to retrieve frame from cache (R:" << clip_time << "| A:" << c->cache_A.offset << "-" << c->cache_A.offset+c->cache_size-1 << "| B:" << c->cache_B.offset << "-" << c->cache_B.offset+c->cache_size-1 << "| WA:" << c->cache_A.written << "| WB:" << c->cache_B.written << ")"; - } - } - } - return false; -}*/ - long playhead_to_clip_frame(Clip* c, long playhead) { return (qMax(0L, playhead - c->timeline_in) + c->clip_in); } @@ -308,12 +222,12 @@ long playhead_to_clip_frame(Clip* c, long playhead) { double playhead_to_clip_seconds(Clip* c, long playhead) { // returns time in seconds long clip_frame = playhead_to_clip_frame(c, playhead); - if (c->reverse) clip_frame = c->getMaximumLength() - clip_frame; + if (c->reverse) clip_frame = c->getMaximumLength() - clip_frame - 1; return ((double) clip_frame/c->sequence->frame_rate)*c->speed; } int64_t seconds_to_timestamp(Clip* c, double seconds) { - return qFloor((seconds * c->stream->time_base.den)/c->stream->time_base.num); + return qRound((seconds * c->stream->time_base.den)/c->stream->time_base.num) + c->stream->start_time; } int64_t playhead_to_timestamp(Clip* c, long playhead) { @@ -368,7 +282,7 @@ int retrieve_next_frame(Clip* c, AVFrame* f) { bool is_clip_active(Clip* c, long playhead) { return c->enabled - && c->timeline_in < playhead + ceil(c->sequence->frame_rate) + && c->timeline_in < playhead + ceil(c->sequence->frame_rate*2) && c->timeline_out > playhead && playhead - c->timeline_in + c->clip_in < c->getMaximumLength(); } diff --git a/project/clip.cpp b/project/clip.cpp index 9c066c949..b14775f24 100644 --- a/project/clip.cpp +++ b/project/clip.cpp @@ -79,7 +79,6 @@ void Clip::reset() { open = false; finished_opening = false; pkt_written = false; - reached_end = false; audio_reset = false; frame_sample_index = -1; audio_buffer_write = false; @@ -97,8 +96,7 @@ void Clip::reset_audio() { case MEDIA_TYPE_TONE: audio_reset = true; frame_sample_index = -1; - audio_buffer_write = 0; - reached_end = false; + audio_buffer_write = 0; break; case MEDIA_TYPE_SEQUENCE: { diff --git a/project/clip.h b/project/clip.h index 5b6784135..aa0decfa7 100644 --- a/project/clip.h +++ b/project/clip.h @@ -85,7 +85,7 @@ struct Clip // temporary variables int load_id; bool undeletable; - bool reached_end; // deprecated + bool reached_end; bool pkt_written; bool open; bool finished_opening; diff --git a/ui/timelinewidget.cpp b/ui/timelinewidget.cpp index ea96fa794..81ca9412f 100644 --- a/ui/timelinewidget.cpp +++ b/ui/timelinewidget.cpp @@ -192,8 +192,6 @@ bool same_sign(int a, int b) { } void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) { - qDebug() << "DRAG ENTER CALLED"; - bool import_init = false; QVector media_list; @@ -273,6 +271,8 @@ void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) { if (ms->video_frame_rate != 0) { predicted_new_frame_rate = ms->video_frame_rate; + if (ms->video_interlacing != VIDEO_PROGRESSIVE) predicted_new_frame_rate *= 2; + // only break with a decent frame rate, otherwise there may be a better candidate got_video_values = true; break; diff --git a/ui/viewerwidget.cpp b/ui/viewerwidget.cpp index eb1e5310b..bbbf95d5b 100644 --- a/ui/viewerwidget.cpp +++ b/ui/viewerwidget.cpp @@ -208,14 +208,11 @@ GLuint ViewerWidget::compose_sequence(Clip* nest, bool render_audio) { QVector current_clips; - long endFrame = LONG_MIN; for (int i=0;iclips.size();i++) { Clip* c = s->clips.at(i); // if clip starts within one second and/or hasn't finished yet if (c != NULL) { - endFrame = qMax(endFrame, c->timeline_out); - if (!(nest != NULL && !same_sign(c->track, nest->track))) { bool clip_is_active = false; @@ -267,10 +264,6 @@ GLuint ViewerWidget::compose_sequence(Clip* nest, bool render_audio) { } } } - } - - if (viewer->playing && (s->playhead == endFrame || (s->using_workarea && s->playhead == s->workarea_out))) { - viewer->pause(); } int half_width = s->width/2; @@ -290,8 +283,6 @@ GLuint ViewerWidget::compose_sequence(Clip* nest, bool render_audio) { texture_failed = true; } else { if (c->track < 0) { - glPushMatrix(); - GLuint textureID = 0; int video_width = c->getWidth(); int video_height = c->getHeight(); @@ -308,137 +299,141 @@ GLuint ViewerWidget::compose_sequence(Clip* nest, bool render_audio) { } get_clip_frame(c, playhead); - if (c->texture != NULL) textureID = c->texture->textureId(); + textureID = c->texture->textureId(); } else if (c->media_type == MEDIA_TYPE_SEQUENCE) { textureID = -1; } - if (textureID == 0 && c->media_type != MEDIA_TYPE_SOLID) { - qDebug() << "[WARNING] Texture hasn't been created yet"; - texture_failed = true; - } else if (playhead >= c->timeline_in) { - // start preparing cache - if (c->fbo == NULL) { - c->fbo = new QOpenGLFramebufferObject* [2]; - c->fbo[0] = new QOpenGLFramebufferObject(video_width, video_height); - c->fbo[1] = new QOpenGLFramebufferObject(video_width, video_height); - } + if (!texture_failed) { + if (textureID == 0 && c->media_type != MEDIA_TYPE_SOLID) { + qDebug() << "[WARNING] Texture hasn't been created yet"; + texture_failed = true; + } else if (playhead >= c->timeline_in) { + glPushMatrix(); - // clear fbos - c->fbo[0]->bind(); - glClear(GL_COLOR_BUFFER_BIT); - c->fbo[0]->release(); - c->fbo[1]->bind(); - glClear(GL_COLOR_BUFFER_BIT); - c->fbo[1]->release(); + // start preparing cache + if (c->fbo == NULL) { + c->fbo = new QOpenGLFramebufferObject* [2]; + c->fbo[0] = new QOpenGLFramebufferObject(video_width, video_height); + c->fbo[1] = new QOpenGLFramebufferObject(video_width, video_height); + } - // for nested sequences - if (c->media_type == MEDIA_TYPE_SEQUENCE) textureID = compose_sequence(c, render_audio); + // clear fbos + c->fbo[0]->bind(); + glClear(GL_COLOR_BUFFER_BIT); + c->fbo[0]->release(); + c->fbo[1]->bind(); + glClear(GL_COLOR_BUFFER_BIT); + c->fbo[1]->release(); - glViewport(0, 0, video_width, video_height); + // for nested sequences + if (c->media_type == MEDIA_TYPE_SEQUENCE) textureID = compose_sequence(c, render_audio); - GLuint composite_texture; - if (c->media_type == MEDIA_TYPE_SOLID) { - composite_texture = c->fbo[0]->texture(); - } else { - composite_texture = draw_clip(c->fbo[0], textureID); - } + glViewport(0, 0, video_width, video_height); - bool fbo_switcher = true; + GLuint composite_texture; + if (c->media_type == MEDIA_TYPE_SOLID) { + composite_texture = c->fbo[0]->texture(); + } else { + composite_texture = draw_clip(c->fbo[0], textureID); + } - GLTextureCoords coords; - coords.vertexTopLeftX = coords.vertexBottomLeftX = -video_width/2; - coords.vertexTopLeftY = coords.vertexTopRightY = -video_height/2; - coords.vertexTopRightX = coords.vertexBottomRightX = video_width/2; - coords.vertexBottomLeftY = coords.vertexBottomRightY = video_height/2; - coords.textureTopLeftY = coords.textureTopRightY = coords.textureTopLeftX = coords.textureBottomLeftX = 0; - coords.textureBottomLeftY = coords.textureBottomRightY = coords.textureTopRightX = coords.textureBottomRightX = 1.0; + bool fbo_switcher = true; - if (c->autoscale && (video_width != s->width || video_height != s->height)) { - double width_multiplier = (double) s->width / (double) video_width; - double height_multiplier = (double) s->height / (double) video_height; - double scale_multiplier = qMin(width_multiplier, height_multiplier); - glScalef(scale_multiplier, scale_multiplier, 1); - } + GLTextureCoords coords; + coords.vertexTopLeftX = coords.vertexBottomLeftX = -video_width/2; + coords.vertexTopLeftY = coords.vertexTopRightY = -video_height/2; + coords.vertexTopRightX = coords.vertexBottomRightX = video_width/2; + coords.vertexBottomLeftY = coords.vertexBottomRightY = video_height/2; + coords.textureTopLeftY = coords.textureTopRightY = coords.textureTopLeftX = coords.textureBottomLeftX = 0; + coords.textureBottomLeftY = coords.textureBottomRightY = coords.textureTopRightX = coords.textureBottomRightX = 1.0; - // EFFECT CODE START - for (int j=0;jeffects.size();j++) { - Effect* e = c->effects.at(j); - if (e->is_enabled()) { - double timecode = ((double)(playhead-c->timeline_in+c->clip_in)/(double)c->sequence->frame_rate); - if (e->enable_coords) { - e->process_coords(timecode, coords); - } - if (e->enable_shader || e->enable_superimpose) { - e->startEffect(); - for (int k=0;kgetIterations();k++) { - e->process_shader(timecode); - composite_texture = draw_clip(c->fbo[fbo_switcher], composite_texture); - if (e->enable_superimpose) { - GLuint superimpose_texture = e->process_superimpose(timecode); - if (superimpose_texture != 0) draw_clip(c->fbo[fbo_switcher], superimpose_texture); + if (c->autoscale && (video_width != s->width || video_height != s->height)) { + double width_multiplier = (double) s->width / (double) video_width; + double height_multiplier = (double) s->height / (double) video_height; + double scale_multiplier = qMin(width_multiplier, height_multiplier); + glScalef(scale_multiplier, scale_multiplier, 1); + } + + // EFFECT CODE START + for (int j=0;jeffects.size();j++) { + Effect* e = c->effects.at(j); + if (e->is_enabled()) { + double timecode = ((double)(playhead-c->timeline_in+c->clip_in)/(double)c->sequence->frame_rate); + if (e->enable_coords) { + e->process_coords(timecode, coords); + } + if (e->enable_shader || e->enable_superimpose) { + e->startEffect(); + for (int k=0;kgetIterations();k++) { + e->process_shader(timecode); + composite_texture = draw_clip(c->fbo[fbo_switcher], composite_texture); + if (e->enable_superimpose) { + GLuint superimpose_texture = e->process_superimpose(timecode); + if (superimpose_texture != 0) draw_clip(c->fbo[fbo_switcher], superimpose_texture); + } + fbo_switcher = !fbo_switcher; } - fbo_switcher = !fbo_switcher; } } } - } - if (c->opening_transition != NULL) { - int transition_progress = playhead - c->timeline_in; - if (transition_progress < c->opening_transition->length) { - c->opening_transition->process_transition((double)transition_progress/(double)c->opening_transition->length); + if (c->opening_transition != NULL) { + int transition_progress = playhead - c->timeline_in; + if (transition_progress < c->opening_transition->length) { + c->opening_transition->process_transition((double)transition_progress/(double)c->opening_transition->length); + } } - } - if (c->closing_transition != NULL) { - int transition_progress = c->closing_transition->length - (playhead - c->timeline_in - c->getLength() + c->closing_transition->length); - if (transition_progress < c->closing_transition->length) { - c->closing_transition->process_transition((double)transition_progress/(double)c->closing_transition->length); + if (c->closing_transition != NULL) { + int transition_progress = c->closing_transition->length - (playhead - c->timeline_in - c->getLength() + c->closing_transition->length); + if (transition_progress < c->closing_transition->length) { + c->closing_transition->process_transition((double)transition_progress/(double)c->closing_transition->length); + } } - } - for (int j=0;jeffects.size();j++) { - if ((c->effects.at(j)->enable_shader || c->effects.at(j)->enable_superimpose) && c->effects.at(j)->is_enabled()) { - c->effects.at(j)->endEffect(); + for (int j=0;jeffects.size();j++) { + if ((c->effects.at(j)->enable_shader || c->effects.at(j)->enable_superimpose) && c->effects.at(j)->is_enabled()) { + c->effects.at(j)->endEffect(); + } } - } - // EFFECT CODE END + // EFFECT CODE END - if (nest != NULL) { - nest->fbo[0]->bind(); - glViewport(0, 0, s->width, s->height); - } else if (rendering) { - glViewport(0, 0, s->width, s->height); - } else { - glViewport(0, 0, width(), height()); - } + if (nest != NULL) { + nest->fbo[0]->bind(); + glViewport(0, 0, s->width, s->height); + } else if (rendering) { + glViewport(0, 0, s->width, s->height); + } else { + glViewport(0, 0, width(), height()); + } - glBindTexture(GL_TEXTURE_2D, composite_texture); + glBindTexture(GL_TEXTURE_2D, composite_texture); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glBegin(GL_QUADS); - glTexCoord2f(coords.textureTopLeftX, coords.textureTopLeftY); // top left - glVertex2f(coords.vertexTopLeftX, coords.vertexTopLeftY); // top left - glTexCoord2f(coords.textureTopRightX, coords.textureTopRightY); // top right - glVertex2f(coords.vertexTopRightX, coords.vertexTopRightY); // top right - glTexCoord2f(coords.textureBottomRightX, coords.textureBottomRightY); // bottom right - glVertex2f(coords.vertexBottomRightX, coords.vertexBottomRightY); // bottom right - glTexCoord2f(coords.textureBottomLeftX, coords.textureBottomLeftY); // bottom left - glVertex2f(coords.vertexBottomLeftX, coords.vertexBottomLeftY); // bottom left - glEnd(); + glBegin(GL_QUADS); + glTexCoord2f(coords.textureTopLeftX, coords.textureTopLeftY); // top left + glVertex2f(coords.vertexTopLeftX, coords.vertexTopLeftY); // top left + glTexCoord2f(coords.textureTopRightX, coords.textureTopRightY); // top right + glVertex2f(coords.vertexTopRightX, coords.vertexTopRightY); // top right + glTexCoord2f(coords.textureBottomRightX, coords.textureBottomRightY); // bottom right + glVertex2f(coords.vertexBottomRightX, coords.vertexBottomRightY); // bottom right + glTexCoord2f(coords.textureBottomLeftX, coords.textureBottomLeftY); // bottom left + glVertex2f(coords.vertexBottomLeftX, coords.vertexBottomLeftY); // bottom left + glEnd(); - glBindTexture(GL_TEXTURE_2D, 0); + glBindTexture(GL_TEXTURE_2D, 0); - if (nest != NULL) { - nest->fbo[0]->release(); - if (default_fbo != NULL) default_fbo->bind(); + if (nest != NULL) { + nest->fbo[0]->release(); + if (default_fbo != NULL) default_fbo->bind(); + } + + glPopMatrix(); } } - - glPopMatrix(); } else { if (render_audio) { switch (c->media_type) { @@ -533,4 +528,9 @@ void ViewerWidget::paintGL() { glDisable(GL_BLEND); glDisable(GL_TEXTURE_2D); } while (loop); + + if (viewer->playing + && (viewer->seq->playhead == viewer->seq->getEndFrame() || (viewer->seq->using_workarea && viewer->seq->playhead >= viewer->seq->workarea_out))) { + viewer->pause(); + } }