bic boi video caching rewrite

This commit is contained in:
itsmattkc
2018-10-14 00:19:50 +11:00
parent 2be02c29a8
commit 94efe97929
8 changed files with 243 additions and 189 deletions
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;