From 4c9863a6963bfd39f45ea03725668ee742ba7db4 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 25 Sep 2021 12:44:58 -0700 Subject: [PATCH] implement audio device to pull directly from renderer --- app/audio/audiomanager.cpp | 8 +- app/audio/audiomanager.h | 4 +- app/audio/outputdeviceproxy.cpp | 10 +-- app/audio/outputdeviceproxy.h | 2 +- app/audio/outputmanager.cpp | 4 +- app/audio/outputmanager.h | 2 +- app/codec/samplebuffer.cpp | 6 +- app/render/CMakeLists.txt | 2 + app/render/previewaudiodevice.cpp | 111 +++++++++++++++++++++++++++ app/render/previewaudiodevice.h | 68 +++++++++++++++++ app/render/previewautocacher.cpp | 70 +++++++++-------- app/render/previewautocacher.h | 54 ++++++------- app/widget/viewer/viewer.cpp | 123 ++++++++++++++++++------------ app/widget/viewer/viewer.h | 17 +++-- 14 files changed, 336 insertions(+), 145 deletions(-) create mode 100644 app/render/previewaudiodevice.cpp create mode 100644 app/render/previewaudiodevice.h diff --git a/app/audio/audiomanager.cpp b/app/audio/audiomanager.cpp index 0b4f2fa57..382b6dd8d 100644 --- a/app/audio/audiomanager.cpp +++ b/app/audio/audiomanager.cpp @@ -94,11 +94,8 @@ void AudioManager::PushToOutput(const QByteArray &samples) emit OutputPushed(samples); } -void AudioManager::StartOutput(AudioPlaybackCache *cache, qint64 offset, int playback_speed) +void AudioManager::StartOutput(QIODevice *device, int playback_speed) { - // Create device - QIODevice* device = cache->CreatePlaybackDevice(); - // Move to output manager's thread device->moveToThread(&output_thread_); @@ -107,10 +104,7 @@ void AudioManager::StartOutput(AudioPlaybackCache *cache, qint64 offset, int pla "PullFromDevice", Qt::QueuedConnection, Q_ARG(QIODevice*, device), - Q_ARG(qint64, offset), Q_ARG(int, playback_speed)); - - emit OutputDeviceStarted(cache, offset, playback_speed); } void AudioManager::StopOutput() diff --git a/app/audio/audiomanager.h b/app/audio/audiomanager.h index d71b1c57d..43c4edc29 100644 --- a/app/audio/audiomanager.h +++ b/app/audio/audiomanager.h @@ -68,7 +68,7 @@ public: /** * @brief Start playing audio from AudioPlaybackCache */ - void StartOutput(AudioPlaybackCache* cache, qint64 offset, int playback_speed); + void StartOutput(QIODevice *device, int playback_speed); /** * @brief Stop audio output immediately @@ -93,8 +93,6 @@ signals: void OutputNotified(); - void OutputDeviceStarted(AudioPlaybackCache* cache, qint64 offset, int playback_speed); - void OutputWaveformStarted(const AudioVisualWaveform* waveform, const rational &start, int playback_speed); void AudioParamsChanged(const AudioParams& params); diff --git a/app/audio/outputdeviceproxy.cpp b/app/audio/outputdeviceproxy.cpp index f8da3cb05..712b3f556 100644 --- a/app/audio/outputdeviceproxy.cpp +++ b/app/audio/outputdeviceproxy.cpp @@ -35,24 +35,17 @@ void AudioOutputDeviceProxy::SetParameters(const AudioParams ¶ms) params_ = params; } -void AudioOutputDeviceProxy::SetDevice(QIODevice* device, qint64 offset, int playback_speed) +void AudioOutputDeviceProxy::SetDevice(QIODevice* device, int playback_speed) { - if (device_) { - delete device_; - } - device_ = device; device_->setParent(this); if (!device_->open(QFile::ReadOnly)) { qCritical() << "Failed to open IO device for audio playback"; - delete device_; device_ = nullptr; return; } - device_->seek(offset); - playback_speed_ = playback_speed; if (qAbs(playback_speed_) != 1) { @@ -64,7 +57,6 @@ void AudioOutputDeviceProxy::close() { QIODevice::close(); - delete device_; device_ = nullptr; if (tempo_processor_.IsOpen()) { diff --git a/app/audio/outputdeviceproxy.h b/app/audio/outputdeviceproxy.h index 08022b2b4..453d1b94c 100644 --- a/app/audio/outputdeviceproxy.h +++ b/app/audio/outputdeviceproxy.h @@ -39,7 +39,7 @@ public: void SetParameters(const AudioParams& params); - void SetDevice(QIODevice *device, qint64 offset, int playback_speed); + void SetDevice(QIODevice *device, int playback_speed); virtual void close() override; diff --git a/app/audio/outputmanager.cpp b/app/audio/outputmanager.cpp index 5d7298950..3e032075e 100644 --- a/app/audio/outputmanager.cpp +++ b/app/audio/outputmanager.cpp @@ -90,7 +90,7 @@ void AudioOutputManager::Close() } } -void AudioOutputManager::PullFromDevice(QIODevice *device, qint64 offset, int playback_speed) +void AudioOutputManager::PullFromDevice(QIODevice *device, int playback_speed) { if (!output_) { return; @@ -102,7 +102,7 @@ void AudioOutputManager::PullFromDevice(QIODevice *device, qint64 offset, int pl push_samples_.clear(); // Pull from the device - device_proxy_.SetDevice(device, offset, playback_speed); + device_proxy_.SetDevice(device, playback_speed); device_proxy_.open(QIODevice::ReadOnly); output_->start(&device_proxy_); } diff --git a/app/audio/outputmanager.h b/app/audio/outputmanager.h index 888a0f923..990303bd3 100644 --- a/app/audio/outputmanager.h +++ b/app/audio/outputmanager.h @@ -53,7 +53,7 @@ public slots: * This will clear any pushed samples or QIODevices currently being read and will start reading from this next time * the audio output requests data. */ - void PullFromDevice(QIODevice* device, qint64 offset, int playback_speed); + void PullFromDevice(QIODevice* device, int playback_speed); // Queued void ResetToPushMode(); diff --git a/app/codec/samplebuffer.cpp b/app/codec/samplebuffer.cpp index 4846c96a3..5a60c49a2 100644 --- a/app/codec/samplebuffer.cpp +++ b/app/codec/samplebuffer.cpp @@ -246,13 +246,9 @@ QByteArray SampleBuffer::toPackedData() const float* output_data = reinterpret_cast(packed_data.data()); - int output_index = 0; - for (int j=0;j. + +***/ + +#include "previewaudiodevice.h" + +namespace olive { + +PreviewAudioDevice::PreviewAudioDevice(QObject *parent) +{ + // These pointers are always valid + using_ = &internal_buffer_[0]; + pushing_ = &internal_buffer_[1]; + + // Default to swap being true because we'll have nothing in the main buffer at first + swap_requested_ = true; +} + +PreviewAudioDevice::~PreviewAudioDevice() +{ + close(); +} + +bool PreviewAudioDevice::isSequential() const +{ + return true; +} + +qint64 PreviewAudioDevice::readData(char *data, qint64 maxSize) +{ + if (swap_requested_) { + SwapBuffers(kFullLock); + swap_requested_ = false; + } + + // This function should NEVER touch the buffer in `pushing_` + qint64 copy_length = qMin(maxSize, qint64(using_->size())); + + if (copy_length) { + memcpy(data, using_->constData(), copy_length); + *using_ = using_->mid(copy_length); + + if (using_->isEmpty() && !SwapBuffers(kTryLock)) { + // Ask push function to swap if it can. If it can't, we'll catch it next read. + swap_requested_ = true; + } + } + + return copy_length; +} + +qint64 PreviewAudioDevice::writeData(const char *, qint64) +{ + // No writing to this device + return -1; +} + +void PreviewAudioDevice::Push(const QByteArray &b) +{ + // This function should NEVER touch the buffer in `using_` + QMutexLocker locker(&lock_); + pushing_->append(b); + + // If swap requested, do this now + if (swap_requested_) { + SwapBuffers(kDontLock); + swap_requested_ = false; + } +} + +bool PreviewAudioDevice::SwapBuffers(LockMethod m) +{ + switch (m) { + case kDontLock: + break; + case kTryLock: + if (!lock_.tryLock()) { + return false; + } + break; + case kFullLock: + lock_.lock(); + break; + } + + std::swap(using_, pushing_); + + if (m != kDontLock) { + lock_.unlock(); + } + + return true; +} + +} diff --git a/app/render/previewaudiodevice.h b/app/render/previewaudiodevice.h new file mode 100644 index 000000000..9b4179128 --- /dev/null +++ b/app/render/previewaudiodevice.h @@ -0,0 +1,68 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef PREVIEWAUDIODEVICE_H +#define PREVIEWAUDIODEVICE_H + +#include "previewautocacher.h" + +namespace olive { + +class PreviewAudioDevice : public QIODevice +{ + Q_OBJECT +public: + PreviewAudioDevice(QObject *parent = nullptr); + + virtual ~PreviewAudioDevice() override; + + void StartQueuing(); + + virtual bool isSequential() const override; + + virtual qint64 readData(char *data, qint64 maxSize) override; + + virtual qint64 writeData(const char *, qint64) override; + + void Push(const QByteArray &b); + +private: + enum LockMethod { + kDontLock, + kTryLock, + kFullLock + }; + + bool SwapBuffers(LockMethod m); + + QMutex lock_; + + QByteArray internal_buffer_[2]; + + QByteArray *using_; + QByteArray *pushing_; + + QAtomicInt swap_requested_; + +}; + +} + +#endif // PREVIEWAUDIODEVICE_H diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 18d8367dd..945eab6f8 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #include "previewautocacher.h" #include @@ -61,6 +81,11 @@ RenderTicketPtr PreviewAutoCacher::GetSingleFrame(const rational &t, bool priori return sfr; } +RenderTicketPtr PreviewAutoCacher::GetRangeOfAudio(TimeRange range, bool prioritize) +{ + return RenderAudio(range, false, prioritize); +} + QVector PreviewAutoCacher::GenerateHashes(ViewerOutput *viewer, FrameHashCache* cache, const QVector ×) { QVector hash_data(times.size()); @@ -631,11 +656,7 @@ void PreviewAutoCacher::TryRender() r.set_out(qMin(r.out(), r.in() + 1)); // Start job - RenderTicketWatcher* watcher = new RenderTicketWatcher(); - watcher->setProperty("job", QVariant::fromValue(last_update_time_)); - connect(watcher, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::AudioRendered); - audio_tasks_.insert(watcher, r); - watcher->SetTicket(RenderManager::instance()->RenderAudio(copied_viewer_node_, r, RenderMode::kOffline, true)); + RenderAudio(r, true, false); audio_iterator_.remove(r); } @@ -658,6 +679,18 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(const QByteArray &hash, cons return watcher; } +RenderTicketPtr PreviewAutoCacher::RenderAudio(const TimeRange &r, bool generate_waveforms, bool prioritize) +{ + RenderTicketWatcher* watcher = new RenderTicketWatcher(); + watcher->setProperty("job", QVariant::fromValue(last_update_time_)); + connect(watcher, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::AudioRendered); + audio_tasks_.insert(watcher, r); + + RenderTicketPtr ticket = RenderManager::instance()->RenderAudio(copied_viewer_node_, r, RenderMode::kOffline, generate_waveforms, prioritize); + watcher->SetTicket(ticket); + return ticket; +} + void PreviewAutoCacher::RequeueFrames() { delayed_requeue_timer_.stop(); @@ -887,31 +920,4 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node) } } -PreviewAutoCacher::PlaybackDevice::PlaybackDevice(PreviewAutoCacher *cacher, QObject *parent) : - cacher_(cacher), - current_time_(0) -{ - audio_params_ = viewer()->GetAudioParams(); -} - -bool PreviewAutoCacher::PlaybackDevice::seek(qint64 pos) -{ - // Call super function - if (QIODevice::seek(pos)) { - // Convert bytes to time - current_time_ = audio_params_.bytes_to_time(pos); - - return true; - } - - return false; -} - -qint64 PreviewAutoCacher::PlaybackDevice::size() const -{ - rational audio_length = cacher_->copied_viewer_node_->GetAudioLength(); - - return audio_params_.time_to_bytes(audio_length); -} - } diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h index 04077fd14..a4d15cf82 100644 --- a/app/render/previewautocacher.h +++ b/app/render/previewautocacher.h @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + #ifndef AUTOCACHER_H #define AUTOCACHER_H @@ -30,6 +50,8 @@ public: RenderTicketPtr GetSingleFrame(const rational& t, bool prioritize); + RenderTicketPtr GetRangeOfAudio(TimeRange range, bool prioritize); + /** * @brief Set the viewer node to auto-cache */ @@ -72,41 +94,11 @@ public: void CancelVideoTasks(bool and_wait_for_them_to_finish = false); void CancelAudioTasks(bool and_wait_for_them_to_finish = false); - class PlaybackDevice : public QIODevice - { - public: - PlaybackDevice(PreviewAutoCacher *cacher, QObject *parent = nullptr); - - virtual ~PlaybackDevice() override; - - virtual bool isSequential() const override; - - virtual bool seek(qint64 pos) override; - - virtual qint64 size() const override; - - virtual qint64 readData(char *data, qint64 maxSize) override; - - virtual qint64 writeData(const char *, qint64) override; - - ViewerOutput *viewer() const - { - return cacher_->copied_viewer_node_; - } - - private: - PreviewAutoCacher *cacher_; - - rational current_time_; - - AudioParams audio_params_; - - }; - private: void TryRender(); RenderTicketWatcher *RenderFrame(const QByteArray& hash, const rational &time, bool prioritize, bool texture_only); + RenderTicketPtr RenderAudio(const TimeRange &range, bool generate_waveforms, bool prioritize); /** * @brief Process all changes to internal NodeGraph copy diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index b9cb8aa60..f359cf5d2 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -54,8 +54,10 @@ ViewerWidget::ViewerWidget(QWidget *parent) : playback_speed_(0), color_menu_enabled_(true), time_changed_from_timer_(false), - prequeuing_(false), - active_queue_jobs_(0) + prequeuing_video_(false), + prequeuing_audio_(false), + active_queue_jobs_(0), + audio_playback_device_(nullptr) { // Set up main layout QVBoxLayout* layout = new QVBoxLayout(this); @@ -111,11 +113,6 @@ ViewerWidget::ViewerWidget(QWidget *parent) : connect(controls_, &PlaybackControls::TimeChanged, this, &ViewerWidget::SetTimeAndSignal); layout->addWidget(controls_); - // If audio is invalidated during playback, we wait some time before starting it again - audio_restart_timer_.setInterval(250); - audio_restart_timer_.setSingleShot(true); - connect(&audio_restart_timer_, &QTimer::timeout, this, &ViewerWidget::StartAudioOutput); - // FIXME: Magic number SetScale(48.0); @@ -182,8 +179,6 @@ void ViewerWidget::ConnectNodeEvent(ViewerOutput *n) connect(n, &ViewerOutput::AudioParamsChanged, this, &ViewerWidget::UpdateRendererAudioParameters); connect(n->video_frame_cache(), &FrameHashCache::Invalidated, this, &ViewerWidget::ViewerInvalidatedVideoRange); connect(n->video_frame_cache(), &FrameHashCache::Shifted, this, &ViewerWidget::ViewerShiftedRange); - connect(n->audio_playback_cache(), &AudioPlaybackCache::Invalidated, this, &ViewerWidget::AudioCacheInvalidated); - connect(n->audio_playback_cache(), &AudioPlaybackCache::Validated, this, &ViewerWidget::AudioCacheValidated); connect(n, &ViewerOutput::TextureInputChanged, this, &ViewerWidget::UpdateStack); VideoParams vp = n->GetVideoParams(); @@ -228,8 +223,6 @@ void ViewerWidget::DisconnectNodeEvent(ViewerOutput *n) disconnect(n, &ViewerOutput::AudioParamsChanged, this, &ViewerWidget::UpdateRendererAudioParameters); disconnect(n->video_frame_cache(), &FrameHashCache::Invalidated, this, &ViewerWidget::ViewerInvalidatedVideoRange); disconnect(n->video_frame_cache(), &FrameHashCache::Shifted, this, &ViewerWidget::ViewerShiftedRange); - disconnect(n->audio_playback_cache(), &AudioPlaybackCache::Invalidated, this, &ViewerWidget::AudioCacheInvalidated); - disconnect(n->audio_playback_cache(), &AudioPlaybackCache::Validated, this, &ViewerWidget::AudioCacheValidated); disconnect(n, &ViewerOutput::TextureInputChanged, this, &ViewerWidget::UpdateStack); SetDisplayImage(QVariant()); @@ -408,14 +401,55 @@ void ViewerWidget::ClearVideoAutoCacherQueue() void ViewerWidget::StartAudioOutput() { - AudioPlaybackCache* audio_cache = GetConnectedNode()->audio_playback_cache(); - if (audio_cache->GetParameters().is_valid()) { - AudioManager::instance()->SetOutputParams(audio_cache->GetParameters()); - AudioManager::instance()->StartOutput(audio_cache, - audio_cache->GetParameters().time_to_bytes_per_channel(GetTime()), - playback_speed_); - emit AudioManager::instance()->OutputWaveformStarted(&audio_cache->visual(), - GetTime(), playback_speed_); + AudioParams params = GetConnectedNode()->GetAudioParams(); + + if (params.is_valid()) { + AudioManager::instance()->SetOutputParams(params); + AudioManager::instance()->StartOutput(audio_playback_device_.get(), playback_speed_); + + qDebug() << "STUB: Nothing to send to audio monitor"; + /*emit AudioManager::instance()->OutputWaveformStarted(&audio_cache->visual(), + GetTime(), playback_speed_);*/ + } +} + +void ViewerWidget::QueueNextAudioBuffer() +{ + // NOTE: Hardcoded 2 second interval + TimeRange range(audio_playback_queue_time_, audio_playback_queue_time_ + 2); + audio_playback_queue_time_ = range.out(); + + RenderTicketWatcher *watcher = new RenderTicketWatcher(this); + connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::ReceivedAudioBufferForPlayback); + audio_playback_queue_.push_back(watcher); + watcher->SetTicket(auto_cacher_.GetRangeOfAudio(range, true)); +} + +void ViewerWidget::ReceivedAudioBufferForPlayback() +{ + while (!audio_playback_queue_.empty() && !audio_playback_queue_.front()->IsRunning()) { + RenderTicketWatcher *watcher = audio_playback_queue_.front(); + audio_playback_queue_.pop_front(); + + if (watcher->HasResult()) { + SampleBufferPtr samples = watcher->Get().value(); + if (samples && audio_playback_device_) { + qint64 t = QDateTime::currentMSecsSinceEpoch(); + QByteArray pack = samples->toPackedData(); + qDebug() << "Packing took:" << (QDateTime::currentMSecsSinceEpoch() - t); + audio_playback_device_->Push(pack); + + if (prequeuing_audio_) { + prequeuing_audio_ = false; + FinishPlayPreprocess(); + } + } + } + + // Do this in the loop so that clearing the array effectively prevents a queue + QueueNextAudioBuffer(); + + delete watcher; } } @@ -551,7 +585,7 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) prequeue_length_ = DeterminePlaybackQueueSize(); if (prequeue_length_ > 0) { - prequeuing_ = true; + prequeuing_video_ = true; // We "prioritize" the frames, which means they're pushed to the top of the render queue, // we queue in reverse so that they're still queued in order @@ -568,9 +602,10 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) } } - if (!prequeuing_) { - FinishPlayPreprocess(); - } + audio_playback_device_.reset(new PreviewAudioDevice()); + prequeuing_audio_ = true; + audio_playback_queue_time_ = GetTime(); + QueueNextAudioBuffer(); } void ViewerWidget::PauseInternal() @@ -588,12 +623,16 @@ void ViewerWidget::PauseInternal() playback_queue_.clear(); playback_backup_timer_.stop(); - audio_restart_timer_.stop(); + + audio_playback_device_.reset(nullptr); + qDeleteAll(audio_playback_queue_); + audio_playback_queue_.clear(); UpdateTextureFromNode(); } - prequeuing_ = false; + prequeuing_video_ = false; + prequeuing_audio_ = false; } void ViewerWidget::PushScrubbedAudio() @@ -603,14 +642,14 @@ void ViewerWidget::PushScrubbedAudio() const AudioParams& params = GetConnectedNode()->audio_playback_cache()->GetParameters(); if (params.is_valid()) { - PreviewAutoCacher::PlaybackDevice *audio_src = new PreviewAutoCacher::PlaybackDevice(&auto_cacher_, this); + qDebug() << "STUB: Use PAC audio function directly"; + /*PreviewAudioDevice *audio_src = new PreviewAudioDevice(&auto_cacher_, GetTime()); if (audio_src->open(QIODevice::ReadOnly)) { // FIXME: Hardcoded scrubbing interval (20ms) int size_of_sample = params.time_to_bytes(rational(20, 1000)); // Push audio - audio_src->seek(params.time_to_bytes_per_channel(GetTime())); QByteArray frame_audio = audio_src->read(size_of_sample); AudioManager::instance()->SetOutputParams(params); AudioManager::instance()->PushToOutput(frame_audio); @@ -618,7 +657,7 @@ void ViewerWidget::PushScrubbedAudio() audio_src->close(); } - delete audio_src; + delete audio_src;*/ } } } @@ -714,6 +753,11 @@ RenderTicketPtr ViewerWidget::GetFrame(const rational &t, bool prioritize) void ViewerWidget::FinishPlayPreprocess() { + // Check if we're still waiting for video or audio respectively + if (prequeuing_video_ || prequeuing_audio_) { + return; + } + int64_t playback_start_time = GetTimestamp(); StartAudioOutput(); @@ -858,7 +902,7 @@ void ViewerWidget::RendererGeneratedFrameForQueue() QVariant frame = watcher->Get(); // Ignore this signal if we've paused now - if (IsPlaying() || prequeuing_) { + if (IsPlaying() || prequeuing_video_) { rational ts = watcher->property("time").value(); playback_queue_.AppendTimewise({ts, frame}, playback_speed_); @@ -867,8 +911,8 @@ void ViewerWidget::RendererGeneratedFrameForQueue() window->queue()->AppendTimewise({ts, frame}, playback_speed_); } - if (prequeuing_ && int(playback_queue_.size()) == prequeue_length_) { - prequeuing_ = false; + if (prequeuing_video_ && int(playback_queue_.size()) == prequeue_length_) { + prequeuing_video_ = false; FinishPlayPreprocess(); } } @@ -1323,21 +1367,4 @@ void ViewerWidget::Dropped(QDropEvent *event) } } -void ViewerWidget::AudioCacheInvalidated() -{ - if (IsPlaying()) { - AudioManager::instance()->StopOutput(); - } -} - -void ViewerWidget::AudioCacheValidated() -{ - if (IsPlaying()) { - // This timer will restart audio - AudioManager::instance()->StopOutput(); - audio_restart_timer_.stop(); - audio_restart_timer_.start(); - } -} - } diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index e5f6edc82..a0e91f898 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -32,6 +32,7 @@ #include "common/rational.h" #include "node/output/viewer/viewer.h" #include "panel/scope/scope.h" +#include "render/previewaudiodevice.h" #include "render/previewautocacher.h" #include "threading/threadticketwatcher.h" #include "viewerdisplay.h" @@ -240,7 +241,8 @@ private: ViewerQueue playback_queue_; int64_t playback_queue_next_frame_; - bool prequeuing_; + bool prequeuing_video_; + bool prequeuing_audio_; QList nonqueue_watchers_; @@ -250,10 +252,12 @@ private: PreviewAutoCacher auto_cacher_; - QTimer audio_restart_timer_; - int active_queue_jobs_; + std::unique_ptr audio_playback_device_; + std::list audio_playback_queue_; + rational audio_playback_queue_time_; + static QVector instances_; private slots: @@ -299,11 +303,12 @@ private slots: void Dropped(QDropEvent* event); - void AudioCacheInvalidated(); - void AudioCacheValidated(); - void StartAudioOutput(); + void QueueNextAudioBuffer(); + + void ReceivedAudioBufferForPlayback(); + }; }