From 84347a0988b017cc174ebee35f30623a8f7a664d Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 19 Oct 2018 21:23:15 +1100 Subject: [PATCH] added waveform support in footage viewer and fixed audio regression where sound stopped playing if it hit EOF --- dialogs/exportdialog.cpp | 8 +-- dialogs/exportdialog.h | 2 +- io/exportthread.cpp | 4 +- panels/viewer.cpp | 29 +++++---- panels/viewer.h | 1 - playback/audio.cpp | 4 +- playback/cacher.cpp | 77 ++++++++++++++++------ playback/playback.cpp | 21 +++--- playback/playback.h | 3 +- ui/timelinewidget.cpp | 74 +++++++++++---------- ui/timelinewidget.h | 3 + ui/viewercontainer.cpp | 9 +-- ui/viewercontainer.h | 2 +- ui/viewerwidget.cpp | 134 +++++++++++++++++++++++---------------- ui/viewerwidget.h | 6 +- 15 files changed, 223 insertions(+), 154 deletions(-) diff --git a/dialogs/exportdialog.cpp b/dialogs/exportdialog.cpp index 25bd62aaf..f18dcfaeb 100644 --- a/dialogs/exportdialog.cpp +++ b/dialogs/exportdialog.cpp @@ -322,10 +322,10 @@ void ExportDialog::render_thread_finished() { panel_sequence_viewer->viewer_widget->initializeGL(); } -void ExportDialog::prep_ui_for_render(bool rendering) { - ui->pushButton->setEnabled(!rendering); - ui->pushButton_2->setEnabled(!rendering); - ui->renderCancel->setEnabled(rendering); +void ExportDialog::prep_ui_for_render(bool r) { + ui->pushButton->setEnabled(!r); + ui->pushButton_2->setEnabled(!r); + ui->renderCancel->setEnabled(r); } void ExportDialog::on_pushButton_clicked() { diff --git a/dialogs/exportdialog.h b/dialogs/exportdialog.h index fabd8f963..7505d1b1a 100644 --- a/dialogs/exportdialog.h +++ b/dialogs/exportdialog.h @@ -43,7 +43,7 @@ private: QVector format_acodecs; ExportThread* et; - void prep_ui_for_render(bool rendering); + void prep_ui_for_render(bool r); bool cancelled; }; diff --git a/io/exportthread.cpp b/io/exportthread.cpp index dda505b5f..a961486e2 100644 --- a/io/exportthread.cpp +++ b/io/exportthread.cpp @@ -341,7 +341,7 @@ void ExportThread::run() { QOpenGLFramebufferObject fbo(sequence->width, sequence->height, QOpenGLFramebufferObject::CombinedDepthStencil, GL_TEXTURE_RECTANGLE); fbo.bind(); - panel_sequence_viewer->viewer_widget->rendering = true; + rendering = true; panel_sequence_viewer->viewer_widget->default_fbo = &fbo; long file_audio_samples = 0; @@ -393,7 +393,7 @@ void ExportThread::run() { } panel_sequence_viewer->viewer_widget->default_fbo = NULL; - panel_sequence_viewer->viewer_widget->rendering = false; + rendering = false; fbo.release(); diff --git a/panels/viewer.cpp b/panels/viewer.cpp index 94e7ea423..9dca4c085 100644 --- a/panels/viewer.cpp +++ b/panels/viewer.cpp @@ -23,14 +23,14 @@ extern "C" { #include #include +#include Viewer::Viewer(QWidget *parent) : QDockWidget(parent), ui(new Ui::Viewer), seq(NULL), playing(false), - created_sequence(false), - disable_viewer(false) + created_sequence(false) { ui->setupUi(this); ui->headers->viewer = this; @@ -81,9 +81,7 @@ void Viewer::reset_all_audio() { for (int i=0;iclips.size();i++) { Clip* c = seq->clips.at(i); - if (c != NULL) { - c->reset_audio(); - } + if (c != NULL) c->reset_audio(); } } clear_audio_ibuffer(); @@ -199,17 +197,20 @@ void Viewer::toggle_play() { } void Viewer::play() { + if (panel_sequence_viewer->playing) panel_sequence_viewer->pause(); + if (panel_footage_viewer->playing) panel_footage_viewer->pause(); + if (seq != NULL) { + reset_all_audio(); if (audio_output->format().sampleRate() != seq->audio_frequency || audio_output->format().channelCount() != av_get_channel_layout_nb_channels(seq->audio_layout)) { init_audio(seq); } - reset_all_audio(); playhead_start = seq->playhead; start_msecs = QDateTime::currentMSecsSinceEpoch(); - playback_updater.start(); playing = true; set_playpause_icon(false); + playback_updater.start(); audio_thread->notifyReceiver(); } } @@ -302,7 +303,6 @@ void Viewer::set_media(int type, void* media) { seq->width = 1920; seq->height = 1080; seq->frame_rate = 30; - disable_viewer = true; } if (footage->audio_tracks.size() > 0) { @@ -319,6 +319,13 @@ void Viewer::set_media(int type, void* media) { c->clip_in = 0; c->recalculateMaxLength(); seq->clips.append(c); + + if (footage->video_tracks.size() == 0) { + viewer_widget->waveform = true; + viewer_widget->waveform_clip = c; + viewer_widget->waveform_ms = audio_stream; + viewer_widget->update(); + } } else { seq->audio_frequency = 48000; } @@ -364,7 +371,7 @@ void Viewer::timer_update() { } void Viewer::clean_created_seq() { - disable_viewer = false; + viewer_widget->waveform = false; if (created_sequence) { // TODO delete undo commands referencing this sequence to avoid crashes @@ -388,8 +395,8 @@ void Viewer::set_sequence(bool main, Sequence *s) { ui->headers->setEnabled(!null_sequence); ui->currentTimecode->setEnabled(!null_sequence); - ui->openGLWidget->setEnabled(!null_sequence && !disable_viewer); - ui->openGLWidget->setVisible(!null_sequence && !disable_viewer); + ui->openGLWidget->setEnabled(!null_sequence); + ui->openGLWidget->setVisible(!null_sequence); ui->pushButton->setEnabled(!null_sequence); ui->pushButton_2->setEnabled(!null_sequence); ui->pushButton_3->setEnabled(!null_sequence); diff --git a/panels/viewer.h b/panels/viewer.h index b4148c18e..54806764b 100644 --- a/panels/viewer.h +++ b/panels/viewer.h @@ -74,7 +74,6 @@ private: void set_sequence(bool main, Sequence* s); bool main_sequence; bool created_sequence; - bool disable_viewer; }; #endif // VIEWER_H diff --git a/playback/audio.cpp b/playback/audio.cpp index 31cd6fecf..4d6fcd163 100644 --- a/playback/audio.cpp +++ b/playback/audio.cpp @@ -137,7 +137,7 @@ int AudioSenderThread::send_audio_to_output(int offset, int max) { // send samples to audio monitor cache // TODO make this work for the footage viewer - Sequence* s = NULL; + /*Sequence* s = NULL; if (panel_footage_viewer->playing) { s = panel_footage_viewer->seq; } @@ -169,7 +169,7 @@ int AudioSenderThread::send_audio_to_output(int offset, int max) { panel_timeline->ui->audio_monitor->sample_cache.append(samples); buffer_offset = next_buffer_offset; } - } + }*/ memset(audio_ibuffer+offset, 0, actual_write); diff --git a/playback/cacher.cpp b/playback/cacher.cpp index fc2e531be..5b50f4891 100644 --- a/playback/cacher.cpp +++ b/playback/cacher.cpp @@ -121,13 +121,13 @@ void cache_audio_worker(Clip* c, Clip* nest) { } } else { if (ret == AVERROR_EOF) { +#ifdef AUDIOWARNINGS + qDebug() << "reached EOF while reading"; +#endif // TODO likewise, I'm not sure if this if statement breaks anything (see equivalent section in cache_video_worker) if (!c->reverse) { c->reached_end = true; } else { -#ifdef AUDIOWARNINGS - qDebug() << "reached EOF"; -#endif } } else { qDebug() << "[WARNING] Raw audio frame data could not be retrieved." << ret; @@ -144,7 +144,7 @@ void cache_audio_worker(Clip* c, Clip* nest) { break; } else { #ifdef AUDIOWARNINGS - qDebug() << "reached EOF"; + qDebug() << "reached EOF while pulling from filtergraph"; #endif if (!c->reverse) break; } @@ -172,7 +172,7 @@ void cache_audio_worker(Clip* c, Clip* nest) { (frame->nb_samples * av_get_bytes_per_sample(static_cast(frame->format)) * frame->channels) ); #ifdef AUDIOWARNINGS - qDebug() << "pts:" << c->frame->pts << "dur:" << c->frame->pkt_duration << "rev_target:" << c->rev_target << "offset:" << offset << "limit:" << rev_frame->linesize[0]; + qDebug() << "pts:" << c->frame->pts << "dur:" << c->frame->pkt_duration << "rev_target:" << c->reverse_target << "offset:" << offset << "limit:" << rev_frame->linesize[0]; #endif } @@ -189,7 +189,7 @@ void cache_audio_worker(Clip* c, Clip* nest) { }*/ #ifdef AUDIOWARNINGS - qDebug() << "pre cutoff deets::: rev_frame.pts:" << rev_frame->pts << "rev_frame.nb_samples" << rev_frame->nb_samples << "rev_target:" << c->rev_target; + qDebug() << "pre cutoff deets::: rev_frame.pts:" << rev_frame->pts << "rev_frame.nb_samples" << rev_frame->nb_samples << "rev_target:" << c->reverse_target; #endif rev_frame->nb_samples = qRound(static_cast(c->reverse_target - rev_frame->pts) / c->stream->codecpar->sample_rate * c->sequence->audio_frequency); #ifdef AUDIOWARNINGS @@ -252,7 +252,7 @@ void cache_audio_worker(Clip* c, Clip* nest) { int nb_samples = qRound((target_sts - frame_sts)*c->sequence->audio_frequency); c->frame_sample_index = nb_samples * 4; #ifdef AUDIOWARNINGS - qDebug() << "fsts:" << frame_sts << "tsts:" << target_sts << "nbs:" << nb_samples << "nbb:" << nb_bytes << "rev_targetToSec:" << (c->rev_target * timebase); + qDebug() << "fsts:" << frame_sts << "tsts:" << target_sts << "nbs:" << nb_samples << "nbb:" << nb_bytes << "rev_targetToSec:" << (c->reverse_target * timebase); qDebug() << "fsi-calc:" << c->frame_sample_index; #endif if (c->reverse) c->frame_sample_index = nb_bytes - c->frame_sample_index; @@ -262,7 +262,6 @@ void cache_audio_worker(Clip* c, Clip* nest) { #ifdef AUDIOWARNINGS qDebug() << "fsi-post-post:" << c->frame_sample_index; #endif - if (c->audio_buffer_write == 0) c->audio_buffer_write = get_buffer_offset_from_frame(c->sequence, qMax(timeline_in, c->audio_target_frame)); int offset = (audio_ibuffer_read + 2048) - c->audio_buffer_write; @@ -300,6 +299,7 @@ void cache_audio_worker(Clip* c, Clip* nest) { apply_audio_effects(c, bytes_to_seconds(frame->pts, frame->channels, frame->sample_rate), frame, nb_bytes); c->frame->pts += nb_bytes; c->frame_sample_index = 0; + qDebug() << "2 >> c->audio_target_frame:" << c->audio_target_frame; if (c->audio_buffer_write == 0) c->audio_buffer_write = get_buffer_offset_from_frame(c->sequence, qMax(timeline_in, c->audio_target_frame)); int offset = audio_ibuffer_read - c->audio_buffer_write; if (offset > 0) { @@ -354,11 +354,9 @@ void cache_audio_worker(Clip* c, Clip* nest) { } } -void cache_video_worker(Clip* c) { +void cache_video_worker(Clip* c, long playhead) { int read_ret, send_ret, retr_ret; - // SKIP TYPE SEEK - int limit = c->max_queue_size; if (c->reverse) limit *= 2; @@ -372,7 +370,7 @@ void cache_video_worker(Clip* c) { avcodec_flush_buffers(c->codecCtx); av_seek_frame(c->formatCtx, c->stream->index, qMax(static_cast(0), smallest_pts - quarter_sec), AVSEEK_FLAG_BACKWARD); } else { - smallest_pts = seconds_to_timestamp(c, playhead_to_clip_seconds(c, c->cacher->playhead)); + smallest_pts = seconds_to_timestamp(c, playhead_to_clip_seconds(c, playhead)); } while (true) { @@ -383,6 +381,8 @@ void cache_video_worker(Clip* c) { 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; @@ -391,6 +391,7 @@ void cache_video_worker(Clip* c) { if (read_ret >= 0) { av_frame_unref(c->frame); }*/ + 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; @@ -407,7 +408,7 @@ void cache_video_worker(Clip* c) { av_frame_free(&frame); break; } else { - if (c->reverse && smallest_pts != INT64_MAX && frame->pts >= smallest_pts) { + if (c->reverse && frame->pts >= smallest_pts) { av_frame_free(&frame); break; } else { @@ -416,9 +417,38 @@ void cache_video_worker(Clip* c) { c->queue.append(frame); c->queue_lock.unlock(); - qDebug() << "appended" << frame->pts << "/ min:" << smallest_pts; - - if ((!c->reverse || smallest_pts == INT64_MAX) && c->queue.size() == limit) break; + //break; + if (!c->reverse && c->queue.size() == limit) { + if (rendering) { + // see if we got the frame we needed (used for speed ups primarily) + bool found = false; + for (int i=0;iqueue.size();i++) { + // TODO/NOTE: this will not work on sped up AND reversed video + if (c->queue.at(i)->pts >= smallest_pts) { + found = true; + break; + } + } + if (found) { + break; + } else { + // remove earliest frame and store another + int earliest_frame = 0; + for (int i=1;iqueue.size();i++) { + // TODO/NOTE: this will not work on sped up AND reversed video + if (c->queue.at(i)->pts < c->queue.at(earliest_frame)->pts) { + 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; + } + } } } } @@ -456,7 +486,6 @@ void reset_cache(Clip* c, long target_frame) { qDebug() << "[WARNING] Seeking terminated prematurely"; break; } - 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; @@ -471,15 +500,21 @@ 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); + // seek (target_frame represents timeline timecode in frames, not clip timecode) - 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 + + int64_t timestamp = seconds_to_timestamp(c, playhead_to_clip_seconds(c, target_frame)); if (c->reverse) { c->reverse_target = timestamp; timestamp -= av_q2d(av_inv_q(c->stream->time_base)); #ifdef AUDIOWARNINGS - qDebug() << "seeking to" << timestamp << "(originally" << c->rev_target << ")"; + qDebug() << "seeking to" << timestamp << "(originally" << c->reverse_target << ")"; + } else { + qDebug() << "reset called; seeking to" << timestamp; #endif - } + } av_seek_frame(c->formatCtx, ms->file_index, timestamp, AVSEEK_FLAG_BACKWARD); c->audio_target_frame = target_frame; c->frame_sample_index = -1; @@ -718,7 +753,7 @@ void cache_clip_worker(Clip* clip, long playhead, bool reset, Clip* nest) { switch (clip->media_type) { case MEDIA_TYPE_FOOTAGE: if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { - cache_video_worker(clip); + cache_video_worker(clip, playhead); } else if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { cache_audio_worker(clip, nest); } diff --git a/playback/playback.cpp b/playback/playback.cpp index cc75a0207..414350ad0 100644 --- a/playback/playback.cpp +++ b/playback/playback.cpp @@ -26,6 +26,7 @@ extern "C" { #include bool texture_failed = false; +bool rendering = false; void open_clip(Clip* clip, bool multithreaded) { switch (clip->media_type) { @@ -117,8 +118,6 @@ void get_clip_frame(Clip* c, long playhead) { bool reset = false; - qDebug() << "QS:" << c->queue.size(); - if (c->queue.size() > 0) { if (ms->infinite_length) { target_frame = c->queue.at(0); @@ -140,14 +139,10 @@ void get_clip_frame(Clip* c, long playhead) { target_frame = c->queue.at(closest_frame); c->queue_lock.lock(); for (int i=0;iqueue.size();i++) { - if (c->queue.at(i)->pts != target_frame->pts) { - if ((c->queue.at(i)->pts < target_frame->pts) == (!c->reverse)) { - qDebug() << "removed frame whose pts was" << c->queue.at(i)->pts << "compared to target" << target_frame->pts; - - av_frame_free(&c->queue[i]); // may be a little heavy for the UI thread? - c->queue.removeAt(i); - i--; - } + if (c->queue.at(i)->pts != target_frame->pts && (c->queue.at(i)->pts < target_frame->pts) == (!c->reverse)) { + av_frame_free(&c->queue[i]); // may be a little heavy for the UI thread? + c->queue.removeAt(i); + i--; } } c->queue_lock.unlock(); @@ -166,9 +161,6 @@ void get_clip_frame(Clip* c, long playhead) { reset = true; } - // get more frames - cache_clip(c, playhead, reset, NULL); - if (target_frame == NULL || reset) { // reset cache texture_failed = true; @@ -181,6 +173,9 @@ void get_clip_frame(Clip* c, long playhead) { c->texture->setData(0, QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, target_frame->data[0]); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); } + + // get more frames + cache_clip(c, playhead, reset, NULL); } } diff --git a/playback/playback.h b/playback/playback.h index a803d2eae..da5d55851 100644 --- a/playback/playback.h +++ b/playback/playback.h @@ -10,12 +10,13 @@ struct Sequence; struct AVFrame; extern bool texture_failed; +extern bool rendering; void open_clip(Clip* clip, bool multithreaded); 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); +void cache_video_worker(Clip* c, long playhead); void handle_media(Sequence* sequence, long playhead, bool multithreaded); void reset_cache(Clip* c, long target_frame); void get_clip_frame(Clip* c, long playhead); diff --git a/ui/timelinewidget.cpp b/ui/timelinewidget.cpp index b12142ce0..d58f3c2e3 100644 --- a/ui/timelinewidget.cpp +++ b/ui/timelinewidget.cpp @@ -1777,7 +1777,7 @@ void TimelineWidget::mouseMoveEvent(QMouseEvent *event) { } } -void TimelineWidget::leaveEvent(QEvent *event) { +void TimelineWidget::leaveEvent(QEvent*) { tooltip_timer.stop(); } @@ -1785,6 +1785,37 @@ int color_brightness(int r, int g, int b) { return (0.2126*r + 0.7152*g + 0.0722*b); } +void draw_waveform(Clip* clip, MediaStream* ms, long media_length, QPainter *p, const QRect& clip_rect, int waveform_start, int waveform_limit, double zoom) { + int divider = ms->audio_channels*2; + int channel_height = clip_rect.height()/ms->audio_channels; + + for (int i=waveform_start;iclip_in + ((double) i/zoom))/media_length) * ms->audio_preview.size())/divider)*divider; + + if (clip->reverse) { + waveform_index = ms->audio_preview.size() - waveform_index - (ms->audio_channels * 2); + } + + for (int j=0;jaudio_channels;j++) { + int mid = (config.rectified_waveforms) ? clip_rect.top()+channel_height*(j+1) : clip_rect.top()+channel_height*j+(channel_height/2); + int offset = waveform_index+(j*2); + + if ((offset + 1) < ms->audio_preview.size()) { + qint8 min = (double)ms->audio_preview.at(offset) / 128.0 * (channel_height/2); + qint8 max = (double)ms->audio_preview.at(offset+1) / 128.0 * (channel_height/2); + + if (config.rectified_waveforms) { + p->drawLine(clip_rect.left()+i, mid, clip_rect.left()+i, mid - (max - min)); + } else { + p->drawLine(clip_rect.left()+i, mid+min, clip_rect.left()+i, mid+max); + } + }/* else { + qDebug() << "[WARNING] Tried to reach" << offset + 1 << ", limit:" << ms->audio_preview.size(); + }*/ + } + } +} + void TimelineWidget::paintEvent(QPaintEvent*) { // Draw clips if (sequence != NULL) { @@ -1869,6 +1900,9 @@ void TimelineWidget::paintEvent(QPaintEvent*) { } } } else if (clip_rect.height() > TRACK_MIN_HEIGHT) { + // draw waveform + p.setPen(QColor(80, 80, 80)); + int waveform_start = -qMin(clip_rect.x(), 0); int waveform_limit = qMin(clip_rect.width(), getScreenPointFromFrame(panel_timeline->zoom, media_length - clip->clip_in)); @@ -1879,43 +1913,7 @@ void TimelineWidget::paintEvent(QPaintEvent*) { if (waveform_limit > 0) checkerboard_rect.setLeft(checkerboard_rect.left() + waveform_limit); } - // draw waveform - p.setPen(QColor(80, 80, 80)); - - int divider = ms->audio_channels*2; - int channel_height = clip_rect.height()/ms->audio_channels; - - for (int i=waveform_start;iclip_in + ((double) i/panel_timeline->zoom))/media_length) * ms->audio_preview.size())/divider)*divider; - - if (clip->reverse) { - waveform_index = ms->audio_preview.size() - waveform_index - (ms->audio_channels * 2); - } - - int rectified_height = 0; - - for (int j=0;jaudio_channels;j++) { - int mid = clip_rect.top()+channel_height*j+(channel_height/2); - int offset = waveform_index+(j*2); - - if ((offset + 1) < ms->audio_preview.size()) { - qint8 min = (double)ms->audio_preview.at(offset) / 128.0 * (channel_height/2); - qint8 max = (double)ms->audio_preview.at(offset+1) / 128.0 * (channel_height/2); - - if (config.rectified_waveforms) { - rectified_height += (max - min); - } else { - p.drawLine(clip_rect.left()+i, mid+min, clip_rect.left()+i, mid+max); - } - } else { - qDebug() << "[WARNING] Tried to reach" << offset + 1 << ", limit:" << ms->audio_preview.size(); - } - } - - if (config.rectified_waveforms) { - p.drawLine(clip_rect.left()+i, clip_rect.height(), clip_rect.left()+i, clip_rect.height() - rectified_height); - } - } + draw_waveform(clip, ms, media_length, &p, clip_rect, waveform_start, waveform_limit, panel_timeline->zoom); } } if (draw_checkerboard) { diff --git a/ui/timelinewidget.h b/ui/timelinewidget.h index c6283d320..e1b2a5798 100644 --- a/ui/timelinewidget.h +++ b/ui/timelinewidget.h @@ -13,12 +13,15 @@ struct Sequence; struct Clip; +struct MediaStream; class Timeline; class TimelineAction; class QScrollBar; class SetSelectionsCommand; +class QPainter; bool same_sign(int a, int b); +void draw_waveform(Clip* clip, MediaStream* ms, long media_length, QPainter* p, const QRect& clip_rect, int waveform_start, int waveform_limit, double zoom); class TimelineWidget : public QWidget { Q_OBJECT diff --git a/ui/viewercontainer.cpp b/ui/viewercontainer.cpp index 81ce67454..bbb4d9656 100644 --- a/ui/viewercontainer.cpp +++ b/ui/viewercontainer.cpp @@ -4,10 +4,11 @@ #include // enforces aspect ratio -ViewerContainer::ViewerContainer(QWidget *parent) : QWidget(parent) { - child = NULL; - aspect_ratio = 1; -} +ViewerContainer::ViewerContainer(QWidget *parent) : + QWidget(parent), + child(NULL), + aspect_ratio(1) +{} void ViewerContainer::adjust() { if (child != NULL) { diff --git a/ui/viewercontainer.h b/ui/viewercontainer.h index 678868e3c..2d2bdd0b6 100644 --- a/ui/viewercontainer.h +++ b/ui/viewercontainer.h @@ -13,7 +13,7 @@ public: void adjust(); protected: - void resizeEvent(QResizeEvent *event); + void resizeEvent(QResizeEvent *event); signals: diff --git a/ui/viewerwidget.cpp b/ui/viewerwidget.cpp index f153b114a..e344b26cd 100644 --- a/ui/viewerwidget.cpp +++ b/ui/viewerwidget.cpp @@ -27,10 +27,12 @@ extern "C" { } ViewerWidget::ViewerWidget(QWidget *parent) : - QOpenGLWidget(parent), - rendering(false), - default_fbo(NULL) + QOpenGLWidget(parent), + default_fbo(NULL), + waveform(false) { + setFocusPolicy(Qt::ClickFocus); + QSurfaceFormat format; format.setDepthBufferSize(24); setFormat(format); @@ -175,62 +177,71 @@ GLuint ViewerWidget::compose_sequence(Clip* nest, bool render_audio) { QVector current_clips; + long endFrame = LONG_MIN; for (int i=0;iclips.size();i++) { Clip* c = s->clips.at(i); // if clip starts within one second and/or hasn't finished yet - if (c != NULL && !(nest != NULL && !same_sign(c->track, nest->track))) { - bool clip_is_active = false; + if (c != NULL) { + endFrame = qMax(endFrame, c->timeline_out); - switch (c->media_type) { - case MEDIA_TYPE_FOOTAGE: - { - Media* m = static_cast(c->media); - if (m->ready) { - MediaStream* ms = m->get_stream_from_file_index(c->track < 0, c->media_stream); - if (ms != NULL && is_clip_active(c, playhead)) { - // if thread is already working, we don't want to touch this, - // but we also don't want to hang the UI thread - if (!c->open) { - open_clip(c, !rendering); + if (!(nest != NULL && !same_sign(c->track, nest->track))) { + bool clip_is_active = false; + + switch (c->media_type) { + case MEDIA_TYPE_FOOTAGE: + { + Media* m = static_cast(c->media); + if (m->ready) { + MediaStream* ms = m->get_stream_from_file_index(c->track < 0, c->media_stream); + if (ms != NULL && is_clip_active(c, playhead)) { + // if thread is already working, we don't want to touch this, + // but we also don't want to hang the UI thread + if (!c->open) { + open_clip(c, !rendering); + } + clip_is_active = true; + } else if (c->open) { + close_clip(c); } + } else { + texture_failed = true; + } + } + break; + case MEDIA_TYPE_SEQUENCE: + case MEDIA_TYPE_SOLID: + case MEDIA_TYPE_TONE: + if (is_clip_active(c, playhead)) { + if (!c->open) open_clip(c, !rendering); clip_is_active = true; } else if (c->open) { close_clip(c); } - } else { - texture_failed = true; + break; } - } - break; - case MEDIA_TYPE_SEQUENCE: - case MEDIA_TYPE_SOLID: - case MEDIA_TYPE_TONE: - if (is_clip_active(c, playhead)) { - if (!c->open) open_clip(c, !rendering); - clip_is_active = true; - } else if (c->open) { - close_clip(c); - } - break; - } - if (clip_is_active) { - bool added = false; - for (int j=0;jtrack < c->track) { - current_clips.insert(j, c); - added = true; - break; - } - } - if (!added) { - current_clips.append(c); - } - } - } + if (clip_is_active) { + bool added = false; + for (int j=0;jtrack < c->track) { + current_clips.insert(j, c); + added = true; + break; + } + } + if (!added) { + current_clips.append(c); + } + } + } + } } + if (viewer->playing && s->playhead == endFrame) { + viewer->pause(); + } + int half_width = s->width/2; int half_height = s->height/2; if (rendering || nest != NULL) half_height = -half_height; @@ -444,28 +455,43 @@ void ViewerWidget::paintGL() { glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); - texture_failed = false; + texture_failed = false; if (!rendering) retry_timer.stop(); - glClear(GL_COLOR_BUFFER_BIT); + glClear(GL_COLOR_BUFFER_BIT); // compose video preview glClearColor(0, 0, 0, 0); compose_sequence(NULL, (viewer->playing || rendering)); - if (texture_failed) { + if (waveform) { + double waveform_zoom = (double) waveform_ms->audio_preview.size() / (double) width(); + double timeline_zoom = (double) width() / (double) waveform_clip->timeline_out; + + QPainter p(this); + p.setPen(QColor(0, 255, 0)); + if (viewer->seq->using_workarea) { + p.fillRect(QRect(getScreenPointFromFrame(timeline_zoom, viewer->seq->workarea_in), 0, getScreenPointFromFrame(timeline_zoom, viewer->seq->workarea_out-viewer->seq->workarea_in), height()), QColor(255, 255, 255, 64)); + } + draw_waveform(waveform_clip, waveform_ms, waveform_clip->timeline_out, &p, rect(), 0, width(), waveform_zoom); + p.setPen(Qt::red); + int playhead_x = getScreenPointFromFrame(timeline_zoom, viewer->seq->playhead); + p.drawLine(playhead_x, 0, playhead_x, height()); + } + + if (texture_failed) { if (rendering) { qDebug() << "[INFO] Texture failed - looping"; loop = true; - } else { + } else { retry_timer.start(); - } - } + } + } - if (config.show_title_safe_area && !rendering) { - drawTitleSafeArea(); - } + if (config.show_title_safe_area && !rendering) { + drawTitleSafeArea(); + } glDisable(GL_BLEND); glDisable(GL_TEXTURE_2D); diff --git a/ui/viewerwidget.h b/ui/viewerwidget.h index 8ec3f0e23..de19a7e10 100644 --- a/ui/viewerwidget.h +++ b/ui/viewerwidget.h @@ -11,6 +11,7 @@ class Viewer; struct Clip; +struct MediaStream; class QOpenGLFramebufferObject; class ViewerWidget : public QOpenGLWidget @@ -19,11 +20,14 @@ class ViewerWidget : public QOpenGLWidget public: ViewerWidget(QWidget *parent = 0); - bool rendering; void paintGL(); void initializeGL(); Viewer* viewer; + bool waveform; + Clip* waveform_clip; + MediaStream* waveform_ms; + QOpenGLFramebufferObject* default_fbo; protected: void paintEvent(QPaintEvent *e);