diff --git a/panels/timeline.cpp b/panels/timeline.cpp index 3c2ca84e3..888bc9bb0 100644 --- a/panels/timeline.cpp +++ b/panels/timeline.cpp @@ -28,7 +28,7 @@ long refactor_frame_number(long framenumber, double source_frame_rate, double target_frame_rate) { if (source_frame_rate == target_frame_rate) return framenumber; - return qRound(((double)framenumber/source_frame_rate)*target_frame_rate); + return qFloor(((double)framenumber/source_frame_rate)*target_frame_rate); } void draw_selection_rectangle(QPainter& painter, const QRect& rect) { diff --git a/playback/cacher.cpp b/playback/cacher.cpp index a1d2549ca..9a2727d6a 100644 --- a/playback/cacher.cpp +++ b/playback/cacher.cpp @@ -345,9 +345,21 @@ void cache_video_worker(Clip* c, long playhead, ClipCache* cache) { } }*/ + if (!error) { + // setting the cache to written even if it hasn't reached_end prevents playback from + // signaling a seek/reset because it wasn't able to find the frame + cache->written = true; + cache->unread = true; + } + /* new AVFilter solution */ if (!c->reached_end) { while (i < c->cache_size) { + if (c->skip_type == SKIP_TYPE_SEEK) { + long seek_frame = refactor_frame_number(i + cache->offset, c->getMediaFrameRate(), c->getMediaFrameRate()*c->speed); + reset_cache(c, seek_frame); + } + av_frame_unref(cache->frames[i]); int ret = (c->filter_graph == NULL) ? AVERROR(EAGAIN) : av_buffersink_get_frame(c->buffersink_ctx, cache->frames[i]); @@ -381,22 +393,18 @@ void cache_video_worker(Clip* c, long playhead, ClipCache* cache) { } } else { i++; + cache->write_count = i; } } } cache->write_count = i; - if (!error) { - // setting the cache to written even if it hasn't reached_end prevents playback from - // signaling a seek/reset because it wasn't able to find the frame - cache->written = true; - cache->unread = true; - } - cache->mutex.unlock(); } + + void reset_cache(Clip* c, long target_frame) { // if we seek to a whole other place in the timeline, we'll need to reset the cache with new values switch (c->media_type) { @@ -415,11 +423,12 @@ void reset_cache(Clip* c, long target_frame) { // play up to the frame we actually want long retrieved_frame = 0; + target_frame--; AVFrame* temp = av_frame_alloc(); do { retrieve_next_frame(c, temp); if (retrieved_frame == 0) { - if (target_frame != 0) retrieved_frame = floor(temp->pts * timebase * av_q2d(c->stream->avg_frame_rate)); + if (target_frame != -1) retrieved_frame = floor(temp->pts * timebase * av_q2d(c->stream->avg_frame_rate)); } else { retrieved_frame++; } @@ -517,9 +526,25 @@ void open_clip_worker(Clip* clip) { char filter_args[512]; if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + /* SKIP_TYPE_SEEK is used if a video is playing at a speed so fast + * that it is quicker to seek to the next frame than to just play + * up to it (e.g. 2000% speed would require playing and skipping + * 20 frames per frame and it many cases it would be quicker to + * seek to it and cache in memory instead. + * + * TODO there could probably be a better heuristic than + * (speed >= 5) for using seek mode. Experiment with the value + * but also in the future perhaps we could implement a system + * of testing how long it takes to seek vs how long it takes to + * decode a frame and compare them to choose with method. + */ + clip->skip_type = (clip->speed < 5) ? SKIP_TYPE_DISCARD : SKIP_TYPE_SEEK; + // create memory cache for video clip->cache_size = (ms->infinite_length) ? 1 : ceil(av_q2d(clip->stream->avg_frame_rate)/4); // cache is half a second in total +// if (clip->skip_type == SKIP_TYPE_SEEK) clip->cache_size *= 2; + clip->cache_A.frames = new AVFrame* [clip->cache_size]; clip->cache_B.frames = new AVFrame* [clip->cache_size]; @@ -544,7 +569,7 @@ void open_clip_worker(Clip* clip) { enum AVPixelFormat pix_fmts[] = { static_cast(dest_format), AV_PIX_FMT_NONE }; if (av_opt_set_int_list(clip->buffersink_ctx, "pix_fmts", pix_fmts, AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN) < 0) { qDebug() << "[ERROR] Could not set output pixel format"; - } + } bool interlaced = false; if (interlaced) { diff --git a/playback/playback.cpp b/playback/playback.cpp index 1b0b0a88e..fea7e49c5 100644 --- a/playback/playback.cpp +++ b/playback/playback.cpp @@ -115,7 +115,9 @@ bool get_clip_frame(Clip* c, long playhead) { sequence_clip_time = c->getMaximumLength() - sequence_clip_time - 1; } - long clip_time = refactor_frame_number(sequence_clip_time, c->sequence->frame_rate, c->getMediaFrameRate()*c->speed); + 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; @@ -139,28 +141,29 @@ bool get_clip_frame(Clip* c, long playhead) { 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) { - if (c->cache_A.mutex.tryLock()) { // lock in case cacher is still writing to it + if (clip_time < (c->cache_A.offset + c->cache_A.write_count)) { +// if (c->cache_A.mutex.tryLock()) { // lock in case cacher is still writing to it using_cache_A = true; c->cache_A.unread = false; cache = c->cache_A.frames; cache_offset = c->cache_A.offset; - c->cache_A.mutex.unlock(); - } +// c->cache_A.mutex.unlock(); +// } } 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) { - if (c->cache_B.mutex.tryLock()) { // lock in case cacher is still writing to it + if (clip_time < (c->cache_B.offset + c->cache_B.write_count)) { +// if (c->cache_B.mutex.tryLock()) { // lock in case cacher is still writing to it using_cache_B = true; c->cache_B.unread = false; cache = c->cache_B.frames; cache_offset = c->cache_B.offset; - c->cache_B.mutex.unlock(); - } +// c->cache_B.mutex.unlock(); +// } } else { // frame is coming but isn't here yet, no need to reset cache no_frame = true; @@ -201,6 +204,8 @@ bool get_clip_frame(Clip* c, long playhead) { } } + qDebug() << "current_frame is NULL:" << (current_frame == NULL); + if (current_frame != NULL) { // set up opengl texture if (c->texture == NULL) { @@ -216,11 +221,10 @@ bool get_clip_frame(Clip* c, long playhead) { 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 if (!no_frame) { + } 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 << ")"; + if (!no_frame) 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; diff --git a/project/clip.h b/project/clip.h index afa9ec96c..13af3822e 100644 --- a/project/clip.h +++ b/project/clip.h @@ -5,6 +5,9 @@ #include #include +#define SKIP_TYPE_DISCARD 0 +#define SKIP_TYPE_SEEK 1 + class Cacher; class Effect; class Transition; @@ -74,6 +77,7 @@ struct Clip double speed; bool reverse; long calculated_length; + int skip_type; // other variables (should be "duplicated" in copy()) QList effects;