many improvements to the playback algorithm
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
+2
-1
@@ -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;
|
||||
}
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
#include "media.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QtMath>
|
||||
#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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
+11
-9
@@ -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;j<s->clips.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";
|
||||
|
||||
+47
-32
@@ -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<int64_t>(av_q2d(av_inv_q(c->stream->time_base))), static_cast<int64_t>(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;i<c->queue.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<int64_t>(0), smallest_pts - quarter_sec), AVSEEK_FLAG_BACKWARD);
|
||||
c->reached_end = false;
|
||||
int64_t seek_ts = qMax(static_cast<int64_t>(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;i<c->queue.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;i<c->queue.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<Media*>(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;
|
||||
|
||||
|
||||
+51
-137
@@ -25,6 +25,10 @@ extern "C" {
|
||||
#include <QOpenGLPixelTransferOptions>
|
||||
#include <QOpenGLFramebufferObject>
|
||||
|
||||
#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;i<c->queue.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;i<c->queue.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<Media*>(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();
|
||||
}
|
||||
|
||||
+1
-3
@@ -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:
|
||||
{
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
@@ -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<void*> 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;
|
||||
|
||||
+110
-110
@@ -208,14 +208,11 @@ GLuint ViewerWidget::compose_sequence(Clip* nest, bool render_audio) {
|
||||
|
||||
QVector<Clip*> current_clips;
|
||||
|
||||
long endFrame = LONG_MIN;
|
||||
for (int i=0;i<s->clips.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;j<c->effects.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;k<e->getIterations();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;j<c->effects.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;k<e->getIterations();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;j<c->effects.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;j<c->effects.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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user