implement audio device to pull directly from renderer
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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_);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -246,13 +246,9 @@ QByteArray SampleBuffer::toPackedData() const
|
||||
|
||||
float* output_data = reinterpret_cast<float*>(packed_data.data());
|
||||
|
||||
int output_index = 0;
|
||||
|
||||
for (int j=0;j<sample_count_per_channel_;j++) {
|
||||
for (int i=0;i<audio_params_.channel_count();i++) {
|
||||
output_data[output_index] = data_[i][j];
|
||||
|
||||
output_index++;
|
||||
output_data[i*j + i] = data_[i][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ set(OLIVE_SOURCES
|
||||
render/managedcolor.h
|
||||
render/playbackcache.cpp
|
||||
render/playbackcache.h
|
||||
render/previewaudiodevice.cpp
|
||||
render/previewaudiodevice.h
|
||||
render/previewautocacher.cpp
|
||||
render/previewautocacher.h
|
||||
render/renderer.cpp
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/***
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#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
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "previewautocacher.h"
|
||||
|
||||
#include <QApplication>
|
||||
@@ -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::HashData> PreviewAutoCacher::GenerateHashes(ViewerOutput *viewer, FrameHashCache* cache, const QVector<rational> ×)
|
||||
{
|
||||
QVector<HashData> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#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
|
||||
|
||||
@@ -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<SampleBufferPtr>();
|
||||
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<rational>();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<RenderTicketWatcher*> nonqueue_watchers_;
|
||||
|
||||
@@ -250,10 +252,12 @@ private:
|
||||
|
||||
PreviewAutoCacher auto_cacher_;
|
||||
|
||||
QTimer audio_restart_timer_;
|
||||
|
||||
int active_queue_jobs_;
|
||||
|
||||
std::unique_ptr<PreviewAudioDevice> audio_playback_device_;
|
||||
std::list<RenderTicketWatcher*> audio_playback_queue_;
|
||||
rational audio_playback_queue_time_;
|
||||
|
||||
static QVector<ViewerWidget*> instances_;
|
||||
|
||||
private slots:
|
||||
@@ -299,11 +303,12 @@ private slots:
|
||||
|
||||
void Dropped(QDropEvent* event);
|
||||
|
||||
void AudioCacheInvalidated();
|
||||
void AudioCacheValidated();
|
||||
|
||||
void StartAudioOutput();
|
||||
|
||||
void QueueNextAudioBuffer();
|
||||
|
||||
void ReceivedAudioBufferForPlayback();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user