bic boi video caching rewrite
This commit is contained in:
@@ -13,7 +13,7 @@ VolumeEffect::VolumeEffect(Clip* c) : Effect(c, EFFECT_TYPE_AUDIO, AUDIO_VOLUME_
|
||||
EffectRow* volume_row = add_row("Volume:");
|
||||
volume_val = volume_row->add_field(EFFECT_FIELD_DOUBLE);
|
||||
volume_val->set_double_minimum_value(0);
|
||||
volume_val->set_double_maximum_value(400);
|
||||
// volume_val->set_double_maximum_value(1000);
|
||||
|
||||
// set defaults
|
||||
volume_val->set_double_default_value(100);
|
||||
|
||||
+87
-132
@@ -247,7 +247,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_seconds(c, c->audio_target_frame);
|
||||
double target_sts = playhead_to_clip_seconds(c, c->audio_target_frame);
|
||||
double frame_sts = (frame->pts * timebase);
|
||||
int nb_samples = qRound((target_sts - frame_sts)*c->sequence->audio_frequency);
|
||||
c->frame_sample_index = nb_samples * 4;
|
||||
@@ -354,89 +354,43 @@ void cache_audio_worker(Clip* c, Clip* nest) {
|
||||
}
|
||||
}
|
||||
|
||||
void cache_video_worker(Clip* c, long playhead, ClipCache* cache) {
|
||||
cache->mutex.lock();
|
||||
void cache_video_worker(Clip* c) {
|
||||
int read_ret, send_ret, retr_ret;
|
||||
while (c->queue.size() < c->max_queue_size) {
|
||||
AVFrame* frame = av_frame_alloc();
|
||||
|
||||
cache->offset = playhead;
|
||||
|
||||
bool error = false;
|
||||
|
||||
int i = 0;
|
||||
|
||||
/* old swscale solution
|
||||
if (!c->reached_end) {
|
||||
while (i < c->cache_size) {
|
||||
retrieve_next_frame_raw_data(c, cache->frames[i]);
|
||||
|
||||
if (c->reached_end) break;
|
||||
i++;
|
||||
}
|
||||
}*/
|
||||
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]);
|
||||
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
if (ret == AVERROR_EOF) {
|
||||
// TODO sorta hacky? not even sure if "reached_end" serves a proper purpose anymore
|
||||
if (!c->reverse) c->reached_end = true;
|
||||
} else {
|
||||
qDebug() << "[WARNING] Raw frame data could not be retrieved." << ret;
|
||||
error = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (ret != AVERROR_EOF) {
|
||||
qDebug() << "[ERROR] Could not pull from filtergraph";
|
||||
error = true;
|
||||
}
|
||||
while ((retr_ret = av_buffersink_get_frame(c->buffersink_ctx, frame)) == AVERROR(EAGAIN)) {
|
||||
AVFrame* send_frame = c->frame;
|
||||
read_ret = (c->use_existing_frame) ? 0 : retrieve_next_frame(c, send_frame);
|
||||
c->use_existing_frame = false;
|
||||
if (read_ret >= 0 || read_ret == AVERROR_EOF) {
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
qDebug() << "retrieved PTS:" << cache->frames[i]->pts << c->stream->time_base.num << "/" << c->stream->time_base.den;
|
||||
|
||||
cache->written = true;
|
||||
i++;
|
||||
cache->write_count = i;
|
||||
qDebug() << "[ERROR] Failed to read frame." << read_ret;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (retr_ret < 0) {
|
||||
qDebug() << "[ERROR] Failed to retrieve frame from buffersink." << retr_ret;
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -445,48 +399,63 @@ void reset_cache(Clip* c, long target_frame) {
|
||||
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) {
|
||||
// flush ffmpeg codecs
|
||||
avcodec_flush_buffers(c->codecCtx);
|
||||
|
||||
// flush filtergraph
|
||||
/*while (av_buffersink_get_frame(c->buffersink_ctx, temp) >= 0) {
|
||||
qDebug() << "flushed?";
|
||||
av_frame_unref(temp);
|
||||
}*/
|
||||
/*av_buffersrc_add_frame(c->buffersrc_ctx, NULL);
|
||||
av_frame_unref(c->frame);
|
||||
while (av_buffersink_get_frame(c->buffersink_ctx, c->frame) >= 0) {
|
||||
av_frame_unref(c->frame);
|
||||
}*/
|
||||
|
||||
double timebase = av_q2d(c->stream->time_base);
|
||||
|
||||
if (c->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
if (target_frame > 0) {
|
||||
AVFrame* temp = av_frame_alloc();
|
||||
// clear current queue
|
||||
c->clear_queue();
|
||||
|
||||
// seeks to nearest keyframe (target_frame represents internal clip frame)
|
||||
int64_t seek_ts = qRound(clip_frame_to_seconds(c, target_frame) / timebase);
|
||||
qDebug() << "requesting: " << seek_ts;
|
||||
av_seek_frame(c->formatCtx, ms->file_index, seek_ts, AVSEEK_FLAG_BACKWARD);
|
||||
// seeks to nearest keyframe (target_frame represents internal clip frame)
|
||||
int64_t target_ts = seconds_to_timestamp(c, playhead_to_clip_seconds(c, target_frame));
|
||||
// if (ms->video_interlacing != VIDEO_PROGRESSIVE) target_ts *= 2;
|
||||
int64_t seek_ts = target_ts;
|
||||
|
||||
// play up to the frame we actually want
|
||||
int ret;
|
||||
do {
|
||||
ret = retrieve_next_frame(c, temp);
|
||||
int64_t timebase_half_second = qRound(av_q2d(av_inv_q(c->stream->time_base)));
|
||||
while (true) {
|
||||
// flush ffmpeg codecs
|
||||
avcodec_flush_buffers(c->codecCtx);
|
||||
|
||||
if (seek_ts > 0) {
|
||||
av_seek_frame(c->formatCtx, ms->file_index, seek_ts, AVSEEK_FLAG_BACKWARD);
|
||||
|
||||
// play up to the frame we actually want
|
||||
/*int ret;
|
||||
do {
|
||||
av_frame_unref(c->frame);
|
||||
ret = retrieve_next_frame(c, c->frame);
|
||||
if (ret < 0) {
|
||||
qDebug() << "[WARNING] Seeking terminated prematurely";
|
||||
break;
|
||||
}
|
||||
qDebug() << "ret pts:" << c->frame->pts << "dur:" << c->frame->pkt_duration << "target:" << target_ts;
|
||||
} while (c->frame->pts + c->frame->pkt_duration + c->frame->pkt_duration < target_ts);
|
||||
if (c->frame->pts <= target_ts) {
|
||||
c->use_existing_frame = true;
|
||||
break;
|
||||
} else {
|
||||
seek_ts -= timebase_second;
|
||||
}*/
|
||||
av_frame_unref(c->frame);
|
||||
int ret = retrieve_next_frame(c, c->frame);
|
||||
if (ret < 0) {
|
||||
qDebug() << "[WARNING] Seeking terminated prematurely";
|
||||
break;
|
||||
}
|
||||
} while (temp->pts + temp->pkt_duration + temp->pkt_duration < seek_ts);
|
||||
av_frame_unref(temp);
|
||||
av_frame_free(&temp);
|
||||
} else {
|
||||
av_seek_frame(c->formatCtx, ms->file_index, 0, AVSEEK_FLAG_BACKWARD);
|
||||
qDebug() << "seek ts:" << seek_ts << "target ts:" << target_ts << "retrieved ts:" << c->frame->pts;
|
||||
if (c->frame->pts <= target_ts) {
|
||||
c->use_existing_frame = true;
|
||||
break;
|
||||
} else {
|
||||
seek_ts -= timebase_half_second;
|
||||
}
|
||||
} else {
|
||||
av_frame_unref(c->frame);
|
||||
av_seek_frame(c->formatCtx, ms->file_index, 0, AVSEEK_FLAG_BACKWARD);
|
||||
c->use_existing_frame = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (c->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
|
||||
// seek (target_frame represents timeline timecode in frames, not clip timecode)
|
||||
int64_t timestamp = qRound(playhead_to_seconds(c, target_frame) / timebase); // TODO qRound here might lead to clicking? or might fix it... who knows
|
||||
int64_t timestamp = seconds_to_timestamp(c, playhead_to_clip_seconds(c, target_frame)); // TODO qRound here might lead to clicking? or might fix it... who knows
|
||||
if (c->reverse) {
|
||||
c->rev_target = timestamp;
|
||||
timestamp -= av_q2d(av_inv_q(c->stream->time_base));
|
||||
@@ -550,6 +519,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 = 30;
|
||||
|
||||
AVDictionary* opts = NULL;
|
||||
|
||||
// optimized decoding settings
|
||||
@@ -597,14 +568,6 @@ void open_clip_worker(Clip* clip) {
|
||||
// if (clip->skip_type == SKIP_TYPE_SEEK) clip->cache_size *= 2;
|
||||
if (ms->video_interlacing != VIDEO_PROGRESSIVE) clip->cache_size *= 2;
|
||||
|
||||
clip->cache_A.frames = new AVFrame* [clip->cache_size];
|
||||
clip->cache_B.frames = new AVFrame* [clip->cache_size];
|
||||
|
||||
for (int i=0;i<clip->cache_size;i++) {
|
||||
clip->cache_A.frames[i] = av_frame_alloc();
|
||||
clip->cache_B.frames[i] = av_frame_alloc();
|
||||
}
|
||||
|
||||
snprintf(filter_args, sizeof(filter_args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
|
||||
clip->stream->codecpar->width,
|
||||
clip->stream->codecpar->height,
|
||||
@@ -750,7 +713,7 @@ void open_clip_worker(Clip* clip) {
|
||||
qDebug() << "[INFO] Clip opened on track" << clip->track;
|
||||
}
|
||||
|
||||
void cache_clip_worker(Clip* clip, long playhead, bool write_A, bool write_B, bool reset, Clip* nest) {
|
||||
void cache_clip_worker(Clip* clip, long playhead, bool reset, Clip* nest) {
|
||||
if (reset) {
|
||||
// note: for video, playhead is in "internal clip" frames - for audio, it's the timeline playhead
|
||||
reset_cache(clip, playhead);
|
||||
@@ -760,14 +723,7 @@ void cache_clip_worker(Clip* clip, long playhead, bool write_A, bool write_B, bo
|
||||
switch (clip->media_type) {
|
||||
case MEDIA_TYPE_FOOTAGE:
|
||||
if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
if (write_A) {
|
||||
cache_video_worker(clip, playhead, &clip->cache_A);
|
||||
playhead += clip->cache_size;
|
||||
}
|
||||
|
||||
if (write_B) {
|
||||
cache_video_worker(clip, playhead, &clip->cache_B);
|
||||
}
|
||||
cache_video_worker(clip);
|
||||
} else if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
|
||||
cache_audio_worker(clip, nest);
|
||||
}
|
||||
@@ -782,8 +738,7 @@ void close_clip_worker(Clip* clip) {
|
||||
clip->finished_opening = false;
|
||||
|
||||
if (clip->media_type == MEDIA_TYPE_FOOTAGE) {
|
||||
// closes ffmpeg file handle and frees any memory used for caching
|
||||
MediaStream* ms = static_cast<Media*>(clip->media)->get_stream_from_file_index(clip->track < 0, clip->media_stream);
|
||||
clip->clear_queue();
|
||||
|
||||
avfilter_graph_free(&clip->filter_graph);
|
||||
|
||||
@@ -791,12 +746,12 @@ void close_clip_worker(Clip* clip) {
|
||||
avcodec_free_context(&clip->codecCtx);
|
||||
avformat_close_input(&clip->formatCtx);
|
||||
|
||||
for (int i=0;i<clip->cache_size;i++) {
|
||||
av_frame_free(&clip->cache_A.frames[i]);
|
||||
if (!ms->infinite_length && clip->track < 0) av_frame_free(&clip->cache_B.frames[i]);
|
||||
if (clip->track >= 0) {
|
||||
for (int i=0;i<clip->cache_size;i++) {
|
||||
av_frame_free(&clip->cache_A.frames[i]);
|
||||
}
|
||||
delete [] clip->cache_A.frames;
|
||||
}
|
||||
delete [] clip->cache_A.frames;
|
||||
if (!ms->infinite_length) delete [] clip->cache_B.frames;
|
||||
}
|
||||
|
||||
av_frame_free(&clip->frame);
|
||||
@@ -820,7 +775,7 @@ void Cacher::run() {
|
||||
if (!caching) {
|
||||
break;
|
||||
} else {
|
||||
cache_clip_worker(clip, playhead, write_A, write_B, reset, nest);
|
||||
cache_clip_worker(clip, playhead, reset, nest);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-3
@@ -16,8 +16,6 @@ public:
|
||||
|
||||
// must be set before caching
|
||||
long playhead;
|
||||
bool write_A;
|
||||
bool write_B;
|
||||
bool reset;
|
||||
Clip* nest;
|
||||
|
||||
@@ -26,7 +24,7 @@ private:
|
||||
};
|
||||
|
||||
void open_clip_worker(Clip* clip);
|
||||
void cache_clip_worker(Clip* clip, long playhead, bool write_A, bool write_B, bool reset, Clip *nest);
|
||||
void cache_clip_worker(Clip* clip, long playhead, bool reset, Clip *nest);
|
||||
void close_clip_worker(Clip* clip);
|
||||
|
||||
#endif // CACHER_H
|
||||
|
||||
+113
-24
@@ -18,7 +18,7 @@ extern "C" {
|
||||
#include <libswresample/swresample.h>
|
||||
}
|
||||
|
||||
#include <algorithm>
|
||||
#include <QtMath>
|
||||
#include <QObject>
|
||||
#include <QOpenGLTexture>
|
||||
#include <QDebug>
|
||||
@@ -37,7 +37,7 @@ void open_clip(Clip* clip, bool multithreaded) {
|
||||
// maybe keep cacher instance in memory while clip exists for performance?
|
||||
clip->cacher = new Cacher(clip);
|
||||
QObject::connect(clip->cacher, SIGNAL(finished()), clip->cacher, SLOT(deleteLater()));
|
||||
clip->cacher->start((clip->track < 0) ? QThread::LowPriority : QThread::TimeCriticalPriority);
|
||||
clip->cacher->start((clip->track < 0) ? QThread::NormalPriority : QThread::TimeCriticalPriority);
|
||||
}
|
||||
} else {
|
||||
clip->finished_opening = false;
|
||||
@@ -89,23 +89,116 @@ void close_clip(Clip* clip) {
|
||||
}
|
||||
}
|
||||
|
||||
void cache_clip(Clip* clip, long playhead, bool write_A, bool write_B, bool reset, Clip* nest) {
|
||||
void cache_clip(Clip* clip, long playhead, bool reset, Clip* nest) {
|
||||
if (clip->media_type == MEDIA_TYPE_FOOTAGE || clip->media_type == MEDIA_TYPE_TONE) {
|
||||
if (clip->multithreaded) {
|
||||
clip->cacher->playhead = playhead;
|
||||
clip->cacher->write_A = write_A;
|
||||
clip->cacher->write_B = write_B;
|
||||
clip->cacher->reset = reset;
|
||||
clip->cacher->nest = nest;
|
||||
|
||||
clip->can_cache.wakeAll();
|
||||
} else {
|
||||
cache_clip_worker(clip, playhead, write_A, write_B, reset, nest);
|
||||
cache_clip_worker(clip, playhead, reset, nest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool get_clip_frame(Clip* c, long playhead) {
|
||||
void get_clip_frame(Clip* c, long playhead) {
|
||||
if (c->finished_opening) {
|
||||
MediaStream* ms = static_cast<Media*>(c->media)->get_stream_from_file_index(c->track < 0, c->media_stream);
|
||||
|
||||
int64_t target_pts = playhead_to_timestamp(c, playhead);
|
||||
int64_t second_pts = qRound(av_q2d(av_inv_q(c->stream->time_base)));
|
||||
if (ms->video_interlacing != VIDEO_PROGRESSIVE) {
|
||||
target_pts *= 2;
|
||||
second_pts *= 2;
|
||||
}
|
||||
AVFrame* target_frame = NULL;
|
||||
|
||||
// qDebug() << "target pts:" << target_pts;
|
||||
|
||||
bool reset = false;
|
||||
|
||||
/*while (c->queue.size() > 0) {
|
||||
if (c->queue.first()->pts == target_pts) {
|
||||
target_frame = c->queue.first();
|
||||
|
||||
qDebug() << "GCF ==> USE PRECISE";
|
||||
break;
|
||||
} else if (c->queue.size() > 1 && c->queue.at(1)->pts > target_pts) {
|
||||
// use this frame
|
||||
target_frame = c->queue.first();
|
||||
|
||||
qDebug() << "GCF ==> USE IMPRECISE";
|
||||
break;
|
||||
} else {
|
||||
|
||||
break;
|
||||
} else {
|
||||
// skip this frame
|
||||
c->queue_mutex.lock(); // thread safety when removing from queue
|
||||
av_frame_free(&c->queue.first()); // may be a little heavy for the UI thread?
|
||||
c->queue.removeFirst();
|
||||
c->queue_mutex.unlock();
|
||||
qDebug() << "GCF ==> SKIP";
|
||||
}
|
||||
}*/
|
||||
|
||||
if (c->queue.size() > 0) {
|
||||
// correct frame may be somewhere else in the queue
|
||||
int closest_frame = 0;
|
||||
for (int i=1;i<c->queue.size();i++) {
|
||||
if (c->queue.at(i)->pts == target_pts) {
|
||||
qDebug() << "GCF ==> USE PRECISE";
|
||||
closest_frame = i;
|
||||
break;
|
||||
} else if (c->queue.at(i)->pts > c->queue.at(closest_frame)->pts && c->queue.at(i)->pts < target_pts) {
|
||||
closest_frame = i;
|
||||
}
|
||||
}
|
||||
|
||||
// remove all frames earlier than this one from the queue
|
||||
target_frame = c->queue.at(closest_frame);
|
||||
c->queue_lock.lock();
|
||||
for (int i=0;i<c->queue.size();i++) {
|
||||
if (c->queue.at(i)->pts < 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";
|
||||
}
|
||||
}
|
||||
|
||||
// get more frames
|
||||
cache_clip(c, playhead, reset, NULL);
|
||||
|
||||
if (target_frame == NULL || reset) {
|
||||
// reset cache
|
||||
texture_failed = true;
|
||||
qDebug() << "[INFO] Cache couldn't keep up - either the user seeked or the system is overloaded";
|
||||
}
|
||||
|
||||
if (target_frame != NULL) {
|
||||
// add gate if this is the same frame
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, target_frame->linesize[0]/4);
|
||||
c->texture->setData(0, QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, target_frame->data[0]);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*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);
|
||||
@@ -225,30 +318,25 @@ bool get_clip_frame(Clip* c, long playhead) {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}*/
|
||||
|
||||
long playhead_to_clip_frame(Clip* c, long playhead) {
|
||||
return (qMax(0L, playhead - c->timeline_in) + c->clip_in);
|
||||
}
|
||||
|
||||
double playhead_to_seconds(Clip* c, long playhead) {
|
||||
double playhead_to_clip_seconds(Clip* c, long playhead) {
|
||||
// returns time in seconds
|
||||
if (c->reverse) {
|
||||
return ((c->getMaximumLength() - (qMax(0L, playhead - c->timeline_in) + c->clip_in))/c->sequence->frame_rate)*c->speed;
|
||||
} else {
|
||||
return ((qMax(0L, playhead - c->timeline_in) + c->clip_in)/c->sequence->frame_rate)*c->speed;
|
||||
}
|
||||
long clip_frame = playhead_to_clip_frame(c, playhead);
|
||||
if (c->reverse) clip_frame = c->getMaximumLength() - clip_frame;
|
||||
return ((double) clip_frame/c->sequence->frame_rate)*c->speed;
|
||||
}
|
||||
|
||||
long seconds_to_clip_frame(Clip* c, double seconds) {
|
||||
// returns time as frame number (according to clip's frame rate)
|
||||
if (c->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
return floor(seconds*c->getMediaFrameRate());
|
||||
} else {
|
||||
qDebug() << "[ERROR] seconds_to_clip_frame only works on video streams";
|
||||
return 0;
|
||||
}
|
||||
int64_t seconds_to_timestamp(Clip* c, double seconds) {
|
||||
return qFloor((seconds * c->stream->time_base.den)/c->stream->time_base.num);
|
||||
}
|
||||
|
||||
double clip_frame_to_seconds(Clip* c, long clip_frame) {
|
||||
// returns frame number in decimal seconds
|
||||
return (double) clip_frame / c->getMediaFrameRate();
|
||||
int64_t playhead_to_timestamp(Clip* c, long playhead) {
|
||||
return seconds_to_timestamp(c, playhead_to_clip_seconds(c, playhead));
|
||||
}
|
||||
|
||||
int retrieve_next_frame(Clip* c, AVFrame* f) {
|
||||
@@ -256,6 +344,7 @@ int retrieve_next_frame(Clip* c, AVFrame* f) {
|
||||
int receive_ret;
|
||||
|
||||
// do we need to retrieve a new packet for a new frame?
|
||||
av_frame_unref(f);
|
||||
while ((receive_ret = avcodec_receive_frame(c->codecCtx, f)) == AVERROR(EAGAIN)) {
|
||||
int read_ret = 0;
|
||||
do {
|
||||
|
||||
+9
-6
@@ -12,16 +12,19 @@ struct AVFrame;
|
||||
extern bool texture_failed;
|
||||
|
||||
void open_clip(Clip* clip, bool multithreaded);
|
||||
void cache_clip(Clip* clip, long playhead, bool write_A, bool write_B, bool reset, Clip *nest);
|
||||
void cache_clip(Clip* clip, long playhead, bool reset, Clip *nest);
|
||||
void close_clip(Clip* clip);
|
||||
void cache_audio_worker(Clip* c, bool write_A);
|
||||
void cache_video_worker(Clip* c, long playhead, ClipCache* cache);
|
||||
void cache_video_worker(Clip* c);
|
||||
void handle_media(Sequence* sequence, long playhead, bool multithreaded);
|
||||
void reset_cache(Clip* c, long target_frame);
|
||||
bool get_clip_frame(Clip* c, long playhead);
|
||||
double playhead_to_seconds(Clip* c, long playhead);
|
||||
long seconds_to_clip_frame(Clip* c, double seconds);
|
||||
double clip_frame_to_seconds(Clip* c, long clip_frame);
|
||||
void get_clip_frame(Clip* c, long playhead);
|
||||
|
||||
long playhead_to_clip_frame(Clip* c, long playhead);
|
||||
double playhead_to_clip_seconds(Clip* c, long playhead);
|
||||
int64_t seconds_to_timestamp(Clip* c, double seconds);
|
||||
int64_t playhead_to_timestamp(Clip* c, long playhead);
|
||||
|
||||
int retrieve_next_frame(Clip* c, AVFrame* f);
|
||||
bool is_clip_active(Clip* c, long playhead);
|
||||
void get_next_audio(Clip* c, bool mix);
|
||||
|
||||
+10
-11
@@ -36,7 +36,8 @@ Clip::Clip(Sequence* s) :
|
||||
fbo(NULL),
|
||||
autoscale(config.autoscale_by_default),
|
||||
maintain_audio_pitch(false),
|
||||
reverse(false)
|
||||
reverse(false),
|
||||
use_existing_frame(false)
|
||||
{
|
||||
reset();
|
||||
}
|
||||
@@ -75,17 +76,10 @@ Clip* Clip::copy(Sequence* s) {
|
||||
}
|
||||
|
||||
void Clip::reset() {
|
||||
cache_size = false;
|
||||
audio_just_reset = false;
|
||||
cache_A.offset = false;
|
||||
cache_B.offset = false;
|
||||
open = false;
|
||||
finished_opening = false;
|
||||
pkt_written = false;
|
||||
cache_A.written = false;
|
||||
cache_B.written = false;
|
||||
cache_A.unread = false;
|
||||
cache_B.unread = false;
|
||||
pkt_written = false;
|
||||
reached_end = false;
|
||||
audio_reset = false;
|
||||
frame_sample_index = -1;
|
||||
@@ -96,8 +90,6 @@ void Clip::reset() {
|
||||
codec = NULL;
|
||||
codecCtx = NULL;
|
||||
texture = NULL;
|
||||
cache_A.frames = NULL;
|
||||
cache_B.frames = NULL;
|
||||
}
|
||||
|
||||
void Clip::reset_audio() {
|
||||
@@ -137,6 +129,13 @@ void Clip::refresh() {
|
||||
recalculateMaxLength();
|
||||
}
|
||||
|
||||
void Clip::clear_queue() {
|
||||
while (queue.size() > 0) {
|
||||
av_frame_free(&queue.first());
|
||||
queue.removeFirst();
|
||||
}
|
||||
}
|
||||
|
||||
Clip::~Clip() {
|
||||
if (open) {
|
||||
close_clip(this);
|
||||
|
||||
+11
-11
@@ -50,6 +50,7 @@ struct Clip
|
||||
void reset_audio();
|
||||
void reset();
|
||||
void refresh();
|
||||
void clear_queue();
|
||||
|
||||
// timeline variables
|
||||
Sequence* sequence;
|
||||
@@ -95,21 +96,24 @@ struct Clip
|
||||
AVPacket* pkt;
|
||||
AVFrame* frame;
|
||||
|
||||
bool pkt_written;
|
||||
bool reached_end;
|
||||
bool reached_end; // deprecated
|
||||
bool pkt_written;
|
||||
bool open;
|
||||
bool finished_opening;
|
||||
bool replaced;
|
||||
|
||||
int64_t rev_target;
|
||||
|
||||
// caching functions
|
||||
// caching functions
|
||||
bool use_existing_frame;
|
||||
bool multithreaded;
|
||||
Cacher* cacher;
|
||||
int cache_size; // deprecated
|
||||
QWaitCondition can_cache;
|
||||
int cache_size;
|
||||
ClipCache cache_A;
|
||||
ClipCache cache_B;
|
||||
int max_queue_size;
|
||||
QVector<AVFrame*> queue;
|
||||
ClipCache cache_A; // deprecated
|
||||
QMutex queue_lock;
|
||||
QMutex lock;
|
||||
QMutex open_lock;
|
||||
|
||||
@@ -130,11 +134,7 @@ struct Clip
|
||||
int audio_buffer_write;
|
||||
bool audio_reset;
|
||||
bool audio_just_reset;
|
||||
long audio_target_frame;
|
||||
|
||||
// statistics
|
||||
int stat_seek_time;
|
||||
int stat_read_time;
|
||||
long audio_target_frame;
|
||||
};
|
||||
|
||||
#endif // CLIP_H
|
||||
|
||||
+11
-1
@@ -255,6 +255,16 @@ GLuint ViewerWidget::compose_sequence(Clip* nest, bool render_audio) {
|
||||
int video_height = c->getHeight();
|
||||
|
||||
if (c->media_type == MEDIA_TYPE_FOOTAGE) {
|
||||
// set up opengl texture
|
||||
if (c->texture == NULL) {
|
||||
c->texture = new QOpenGLTexture(QOpenGLTexture::Target2D);
|
||||
c->texture->setSize(c->stream->codecpar->width, c->stream->codecpar->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);
|
||||
}
|
||||
|
||||
get_clip_frame(c, playhead);
|
||||
if (c->texture != NULL) textureID = c->texture->textureId();
|
||||
} else if (c->media_type == MEDIA_TYPE_SEQUENCE) {
|
||||
@@ -394,7 +404,7 @@ GLuint ViewerWidget::compose_sequence(Clip* nest, bool render_audio) {
|
||||
case MEDIA_TYPE_TONE:
|
||||
if (c->lock.tryLock()) {
|
||||
// clip is not caching, start caching audio
|
||||
cache_clip(c, playhead, false, false, c->audio_reset, nest);
|
||||
cache_clip(c, playhead, c->audio_reset, nest);
|
||||
c->lock.unlock();
|
||||
}
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user