diff --git a/io/config.cpp b/io/config.cpp index d7d609497..0a1870091 100644 --- a/io/config.cpp +++ b/io/config.cpp @@ -26,7 +26,8 @@ Config::Config() enable_drag_files_to_timeline(true), autoscale_by_default(false), recording_mode(2), - enable_seek_to_import(false) + enable_seek_to_import(false), + enable_audio_scrubbing(true) {} void Config::load(QString path) { @@ -91,7 +92,10 @@ void Config::load(QString path) { } else if (stream.name() == "EnableSeekToImport") { stream.readNext(); enable_seek_to_import = (stream.text() == "1"); - } + } else if (stream.name() == "AudioScrubbing") { + stream.readNext(); + enable_audio_scrubbing = (stream.text() == "1"); + } } } if (stream.hasError()) { @@ -133,6 +137,7 @@ void Config::save(QString path) { stream.writeTextElement("AutoscaleByDefault", QString::number(autoscale_by_default)); stream.writeTextElement("RecordingMode", QString::number(recording_mode)); stream.writeTextElement("EnableSeekToImport", QString::number(enable_seek_to_import)); + stream.writeTextElement("AudioScrubbing", QString::number(enable_audio_scrubbing)); stream.writeEndElement(); // configuration stream.writeEndDocument(); // doc diff --git a/io/config.h b/io/config.h index c87b06ee8..5bd3fd88c 100644 --- a/io/config.h +++ b/io/config.h @@ -33,6 +33,7 @@ struct Config { bool autoscale_by_default; int recording_mode; bool enable_seek_to_import; + bool enable_audio_scrubbing; void load(QString path); void save(QString path); diff --git a/mainwindow.cpp b/mainwindow.cpp index fae684765..bd1d7a805 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -649,6 +649,7 @@ void MainWindow::toolMenu_About_To_Be_Shown() { ui->actionEnable_Drag_Files_to_Timeline->setChecked(config.enable_drag_files_to_timeline); ui->actionAuto_scale_by_Default->setChecked(config.autoscale_by_default); ui->actionEnable_Seek_to_Import->setChecked(config.enable_seek_to_import); + ui->actionAudio_Scrubbing->setChecked(config.enable_audio_scrubbing); } void MainWindow::on_actionEdit_Tool_Selects_Links_triggered() { @@ -871,3 +872,7 @@ void MainWindow::on_actionEnable_Disable_Clip_triggered() { void MainWindow::on_actionEnable_Seek_to_Import_triggered() { config.enable_seek_to_import = !config.enable_seek_to_import; } + +void MainWindow::on_actionAudio_Scrubbing_triggered() { + config.enable_audio_scrubbing = !config.enable_audio_scrubbing; +} diff --git a/mainwindow.h b/mainwindow.h index 7695f9772..4f396dfcd 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -189,6 +189,8 @@ private slots: void on_actionEnable_Seek_to_Import_triggered(); + void on_actionAudio_Scrubbing_triggered(); + private: Ui::MainWindow *ui; void setup_layout(); diff --git a/mainwindow.ui b/mainwindow.ui index 3a2ee326e..4dc37e73c 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -33,7 +33,7 @@ 0 0 653 - 20 + 16 @@ -177,6 +177,7 @@ + @@ -806,6 +807,14 @@ Enable Seek to Import + + + true + + + Audio Scrubbing + + diff --git a/panels/viewer.cpp b/panels/viewer.cpp index 568240e4d..7e6e5df58 100644 --- a/panels/viewer.cpp +++ b/panels/viewer.cpp @@ -92,15 +92,18 @@ void Viewer::reset_all_audio() { if (c != NULL) c->reset_audio(); } } - clear_audio_ibuffer(); + clear_audio_ibuffer(); +} + +void Viewer::assert_audio_device() { + if (audio_output->format().sampleRate() != seq->audio_frequency + || audio_output->format().channelCount() != av_get_channel_layout_nb_channels(seq->audio_layout)) { + init_audio(seq); + } } long timecode_to_frame(const QString& s, int view, double frame_rate) { - QList list = s.split(QRegExp("[:;]")); - - for (int i=0;i list = s.split(QRegExp("[:;]")); if (view == TIMECODE_FRAMES || list.size() == 1) { return s.toLong(); @@ -208,6 +211,9 @@ void Viewer::seek(long p) { pause(); seq->playhead = p; update_parents(); + reset_all_audio(); + assert_audio_device(); + audio_scrub = true; } void Viewer::go_to_start() { @@ -270,11 +276,8 @@ void Viewer::play() { 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(); + assert_audio_device(); if (is_recording_cued() && !start_recording()) { dout << "[ERROR] Failed to record audio"; return; @@ -285,7 +288,7 @@ void Viewer::play() { set_playpause_icon(false); playback_updater.start(); timer_update(); - audio_thread->notifyReceiver(); + audio_thread->notifyReceiver(); } } diff --git a/panels/viewer.h b/panels/viewer.h index 93b4727e2..da294f8ce 100644 --- a/panels/viewer.h +++ b/panels/viewer.h @@ -59,6 +59,7 @@ public: int recording_track; void reset_all_audio(); + void assert_audio_device(); void update_parents(); ViewerWidget* viewer_widget; diff --git a/playback/audio.cpp b/playback/audio.cpp index da96cba23..feb0d7aee 100644 --- a/playback/audio.cpp +++ b/playback/audio.cpp @@ -23,6 +23,7 @@ extern "C" { QAudioOutput* audio_output; QIODevice* audio_io_device; bool audio_device_set = false; +bool audio_scrub = false; QMutex audio_write_lock; QAudioInput* audio_input = NULL; QFile output_recording; @@ -33,7 +34,7 @@ int audio_ibuffer_read = 0; long audio_ibuffer_frame = 0; double audio_ibuffer_timecode = 0; -AudioSenderThread* audio_thread; +AudioSenderThread* audio_thread = NULL; void init_audio(Sequence* s) { stop_audio(); @@ -121,7 +122,7 @@ void AudioSenderThread::run() { cond.wait(&lock); if (close) { break; - } else if (panel_sequence_viewer->playing || panel_footage_viewer->playing) { + } else if (panel_sequence_viewer->playing || panel_footage_viewer->playing || audio_scrub) { int written_bytes = 0; int adjusted_read_index = audio_ibuffer_read%audio_ibuffer_size; @@ -132,6 +133,8 @@ void AudioSenderThread::run() { // got all the bytes, write again written_bytes += send_audio_to_output(0, audio_ibuffer_size); } + + audio_scrub = false; } } lock.unlock(); diff --git a/playback/audio.h b/playback/audio.h index be96ed74b..344f503b1 100644 --- a/playback/audio.h +++ b/playback/audio.h @@ -40,6 +40,7 @@ extern qint8 audio_ibuffer[audio_ibuffer_size]; extern int audio_ibuffer_read; extern long audio_ibuffer_frame; extern double audio_ibuffer_timecode; +extern bool audio_scrub; void clear_audio_ibuffer(); void init_audio(Sequence *s); diff --git a/playback/cacher.cpp b/playback/cacher.cpp index 4461dd283..026a7146b 100644 --- a/playback/cacher.cpp +++ b/playback/cacher.cpp @@ -70,15 +70,15 @@ void apply_audio_effects(Clip* c, double timecode_start, AVFrame* frame, int nb_ } } -void cache_audio_worker(Clip* c, Clip* nest) { +void cache_audio_worker(Clip* c, bool scrubbing, Clip* nest) { long timeline_in = c->timeline_in; long timeline_out = c->timeline_out; if (nest != NULL) { timeline_in = refactor_frame_number(timeline_in, c->sequence->frame_rate, sequence->frame_rate) + nest->timeline_in; timeline_out = refactor_frame_number(timeline_out, c->sequence->frame_rate, sequence->frame_rate) + nest->timeline_in; - } + } - while (true) { + while (true) { AVFrame* frame; int nb_bytes = INT_MAX; @@ -239,7 +239,7 @@ void cache_audio_worker(Clip* c, Clip* nest) { } else { // if there is no more data in the file, we flush the remainder out of swresample break; - } + } new_frame = true; @@ -316,17 +316,25 @@ void cache_audio_worker(Clip* c, Clip* nest) { default: // shouldn't ever get here dout << "[ERROR] Tried to cache a non-footage/tone clip"; return; - } + } // mix audio into internal buffer if (frame->nb_samples == 0) { break; } else { long buffer_timeline_out = get_buffer_offset_from_frame(c->sequence, timeline_out); - audio_write_lock.lock(); + audio_write_lock.lock(); while (c->frame_sample_index < nb_bytes && c->audio_buffer_write < audio_ibuffer_read+audio_ibuffer_size-512 && c->audio_buffer_write < buffer_timeline_out) { + + /*dout << "F (" << + c->frame_sample_index << "/" << nb_bytes + << ") (" << + c->audio_buffer_write << "/" << (audio_ibuffer_read+audio_ibuffer_size-512) + << ") (" << + c->audio_buffer_write << "/" << buffer_timeline_out << ")";*/ + int upper_byte_index = (c->audio_buffer_write+1)%audio_ibuffer_size; int lower_byte_index = (c->audio_buffer_write)%audio_ibuffer_size; qint16 old_sample = static_cast((audio_ibuffer[upper_byte_index] & 0xFF) << 8 | (audio_ibuffer[lower_byte_index] & 0xFF)); @@ -345,6 +353,10 @@ void cache_audio_worker(Clip* c, Clip* nest) { audio_write_lock.unlock(); + if (scrubbing) { + audio_thread->notifyReceiver(); + } + if (c->frame_sample_index == nb_bytes) { c->frame_sample_index = -1; } else { @@ -357,6 +369,9 @@ void cache_audio_worker(Clip* c, Clip* nest) { if (c->reached_end) { frame->nb_samples = 0; } + if (scrubbing) { + break; + } } } @@ -786,7 +801,7 @@ void open_clip_worker(Clip* clip) { dout << "[INFO] Clip opened on track" << clip->track; } -void cache_clip_worker(Clip* clip, long playhead, bool reset, Clip* nest) { +void cache_clip_worker(Clip* clip, long playhead, bool reset, bool scrubbing, Clip* nest) { if (reset) { // note: for video, playhead is in "internal clip" frames - for audio, it's the timeline playhead reset_cache(clip, playhead); @@ -798,11 +813,11 @@ void cache_clip_worker(Clip* clip, long playhead, bool reset, Clip* nest) { if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { cache_video_worker(clip, playhead); } else if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { - cache_audio_worker(clip, nest); + cache_audio_worker(clip, scrubbing, nest); } break; case MEDIA_TYPE_TONE: - cache_audio_worker(clip, nest); + cache_audio_worker(clip, scrubbing, nest); break; } } @@ -841,7 +856,7 @@ void Cacher::run() { if (!caching) { break; } else { - cache_clip_worker(clip, playhead, reset, nest); + cache_clip_worker(clip, playhead, reset, scrubbing, nest); } } diff --git a/playback/cacher.h b/playback/cacher.h index 4f02b5794..1a65f6aae 100644 --- a/playback/cacher.h +++ b/playback/cacher.h @@ -17,6 +17,7 @@ public: // must be set before caching long playhead; bool reset; + bool scrubbing; Clip* nest; private: @@ -24,7 +25,7 @@ private: }; void open_clip_worker(Clip* clip); -void cache_clip_worker(Clip* clip, long playhead, bool reset, Clip *nest); +void cache_clip_worker(Clip* clip, long playhead, bool reset, bool scrubbing, Clip *nest); void close_clip_worker(Clip* clip); #endif // CACHER_H diff --git a/playback/playback.cpp b/playback/playback.cpp index 09b418a70..288207f20 100644 --- a/playback/playback.cpp +++ b/playback/playback.cpp @@ -95,16 +95,17 @@ void close_clip(Clip* clip) { } } -void cache_clip(Clip* clip, long playhead, bool reset, Clip* nest) { +void cache_clip(Clip* clip, long playhead, bool reset, bool scrubbing, Clip* nest) { if (clip->media_type == MEDIA_TYPE_FOOTAGE || clip->media_type == MEDIA_TYPE_TONE) { if (clip->multithreaded) { clip->cacher->playhead = playhead; clip->cacher->reset = reset; clip->cacher->nest = nest; + clip->cacher->scrubbing = scrubbing; clip->can_cache.wakeAll(); } else { - cache_clip_worker(clip, playhead, reset, nest); + cache_clip_worker(clip, playhead, reset, scrubbing, nest); } } } @@ -224,7 +225,7 @@ void get_clip_frame(Clip* c, long playhead) { c->queue_lock.unlock(); // get more frames - if (cache) cache_clip(c, playhead, reset, NULL); + if (cache) cache_clip(c, playhead, reset, false, NULL); } } diff --git a/playback/playback.h b/playback/playback.h index da5d55851..4f5067480 100644 --- a/playback/playback.h +++ b/playback/playback.h @@ -13,7 +13,7 @@ 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 cache_clip(Clip* clip, long playhead, bool reset, bool scrubbing, Clip *nest); void close_clip(Clip* clip); void cache_audio_worker(Clip* c, bool write_A); void cache_video_worker(Clip* c, long playhead); diff --git a/ui/viewerwidget.cpp b/ui/viewerwidget.cpp index 1beccbd42..48163a31f 100644 --- a/ui/viewerwidget.cpp +++ b/ui/viewerwidget.cpp @@ -436,14 +436,14 @@ GLuint ViewerWidget::compose_sequence(Clip* nest, bool render_audio) { glPopMatrix(); } } - } else { - if (render_audio) { + } else { + if (render_audio || (config.enable_audio_scrubbing && audio_scrub)) { switch (c->media_type) { case MEDIA_TYPE_FOOTAGE: case MEDIA_TYPE_TONE: if (c->lock.tryLock()) { - // clip is not caching, start caching audio - cache_clip(c, playhead, c->audio_reset, nest); + // clip is not caching, start caching audio + cache_clip(c, playhead, c->audio_reset, !render_audio, nest); c->lock.unlock(); } break; @@ -479,6 +479,7 @@ GLuint ViewerWidget::compose_sequence(Clip* nest, bool render_audio) { } void ViewerWidget::paintGL() { + bool render_audio = (viewer->playing || rendering); bool loop = false; do { loop = false; @@ -496,7 +497,7 @@ void ViewerWidget::paintGL() { // compose video preview glClearColor(0, 0, 0, 0); - compose_sequence(NULL, (viewer->playing || rendering)); + compose_sequence(NULL, render_audio); if (waveform) { double waveform_zoom = (double) waveform_ms->audio_preview.size() / (double) width();