Merge branch 'new-audio-cache'

This commit is contained in:
itsmattkc
2021-09-29 00:32:27 -07:00
29 changed files with 711 additions and 252 deletions
+7 -5
View File
@@ -16,15 +16,17 @@
set(OLIVE_SOURCES
${OLIVE_SOURCES}
audio/audiomanager.h
audio/audiomanager.cpp
audio/audiovisualwaveform.h
audio/audiomanager.h
audio/audiovisualwaveform.cpp
audio/outputdeviceproxy.h
audio/audiovisualwaveform.h
audio/outputdeviceproxy.cpp
audio/outputmanager.h
audio/outputdeviceproxy.h
audio/outputmanager.cpp
audio/tempoprocessor.h
audio/outputmanager.h
audio/packedprocessor.cpp
audio/packedprocessor.h
audio/tempoprocessor.cpp
audio/tempoprocessor.h
PARENT_SCOPE
)
+2 -9
View File
@@ -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(std::shared_ptr<QIODevice> device)
{
// Create device
QIODevice* device = cache->CreatePlaybackDevice();
// Move to output manager's thread
device->moveToThread(&output_thread_);
@@ -106,11 +103,7 @@ void AudioManager::StartOutput(AudioPlaybackCache *cache, qint64 offset, int pla
QMetaObject::invokeMethod(output_manager_,
"PullFromDevice",
Qt::QueuedConnection,
Q_ARG(QIODevice*, device),
Q_ARG(qint64, offset),
Q_ARG(int, playback_speed));
emit OutputDeviceStarted(cache, offset, playback_speed);
Q_ARG(std::shared_ptr<QIODevice>, device));
}
void AudioManager::StopOutput()
+1 -3
View File
@@ -68,7 +68,7 @@ public:
/**
* @brief Start playing audio from AudioPlaybackCache
*/
void StartOutput(AudioPlaybackCache* cache, qint64 offset, int playback_speed);
void StartOutput(std::shared_ptr<QIODevice> device);
/**
* @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);
+20 -10
View File
@@ -27,13 +27,12 @@
namespace olive {
const rational AudioVisualWaveform::kMinimumSampleRate = rational(1, 8);
const rational AudioVisualWaveform::kMaximumSampleRate = 1024;
AudioVisualWaveform::AudioVisualWaveform() :
channels_(0)
{
// Must be a power of 2
static const rational kMinimumSampleRate = rational(1, 8);
static const rational kMaximumSampleRate = 1024;
for (rational i=kMinimumSampleRate; i<=kMaximumSampleRate; i*=2) {
mipmapped_data_.insert({i, Sample()});
}
@@ -278,7 +277,19 @@ AudioVisualWaveform::Sample AudioVisualWaveform::GetSummaryFromTime(const ration
int start_sample = time_to_samples(start, rate_dbl);
int sample_length = time_to_samples(length, rate_dbl);
return ReSumSamples(&using_mipmap->second.constData()[start_sample], sample_length, channels_);
const QVector<AudioVisualWaveform::SamplePerChannel> &mipmap_data = using_mipmap->second;
// Determine if the array actually has this sample
sample_length = qMin(sample_length, mipmap_data.size() - start_sample);
// Based on the above `min`, if sample length <= 0, that means start_sample >= the size of the
// array and nothing can be returned.
if (sample_length > 0) {
return ReSumSamples(&mipmap_data.constData()[start_sample], sample_length, channels_);
}
// Return null samples
return AudioVisualWaveform::Sample(channel_count(), {0, 0});
}
AudioVisualWaveform::Sample AudioVisualWaveform::SumSamples(const float *samples, int nb_samples, int nb_channels)
@@ -431,15 +442,14 @@ int AudioVisualWaveform::time_to_samples(const double &time, double sample_rate)
std::map<rational, AudioVisualWaveform::Sample>::const_iterator AudioVisualWaveform::GetMipmapForScale(double scale) const
{
// Find largest mipmap for this scale (or the largest if we don't find one sufficient)
auto using_mipmap = mipmapped_data_.cend();
using_mipmap--;
for (auto it=mipmapped_data_.cbegin(); it!=mipmapped_data_.cend(); it++) {
if (it->first.toDouble() >= scale) {
using_mipmap = it;
break;
return it;
}
}
return using_mipmap;
// We don't have a mipmap large enough for this scale, so just return the largest we have
return std::prev(mipmapped_data_.cend());
}
void AudioVisualWaveform::ExpandMinMax(AudioVisualWaveform::SamplePerChannel &sum, float value)
+4
View File
@@ -107,6 +107,10 @@ public:
static void DrawWaveform(QPainter* painter, const QRect &rect, const double &scale, const AudioVisualWaveform& samples, const rational &start_time);
// Must be a power of 2
static const rational kMinimumSampleRate;
static const rational kMaximumSampleRate;
private:
static void ExpandMinMax(SamplePerChannel &sum, float value);
+3 -72
View File
@@ -35,41 +35,22 @@ void AudioOutputDeviceProxy::SetParameters(const AudioParams &params)
params_ = params;
}
void AudioOutputDeviceProxy::SetDevice(QIODevice* device, qint64 offset, int playback_speed)
void AudioOutputDeviceProxy::SetDevice(std::shared_ptr<QIODevice> device)
{
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) {
tempo_processor_.Open(params_, qAbs(playback_speed_));
}
}
void AudioOutputDeviceProxy::close()
{
QIODevice::close();
delete device_;
device_ = nullptr;
if (tempo_processor_.IsOpen()) {
tempo_processor_.Close();
}
}
qint64 AudioOutputDeviceProxy::readData(char *data, qint64 maxlen)
@@ -78,26 +59,7 @@ qint64 AudioOutputDeviceProxy::readData(char *data, qint64 maxlen)
return 0;
}
qint64 read_count;
if (tempo_processor_.IsOpen()) {
while ((read_count = tempo_processor_.Pull(data, static_cast<int>(maxlen))) == 0) {
int dev_read = static_cast<int>(ReverseAwareRead(data, maxlen));
if (!dev_read) {
break;
}
tempo_processor_.Push(data, dev_read);
}
} else {
// If we aren't doing any tempo processing, simply passthrough the read signal
read_count = ReverseAwareRead(data, maxlen);
}
return read_count;
return device_->read(data, maxlen);
}
qint64 AudioOutputDeviceProxy::writeData(const char *data, qint64 maxSize)
@@ -105,38 +67,7 @@ qint64 AudioOutputDeviceProxy::writeData(const char *data, qint64 maxSize)
Q_UNUSED(data)
Q_UNUSED(maxSize)
return 0;
}
qint64 AudioOutputDeviceProxy::ReverseAwareRead(char *data, qint64 maxlen)
{
qint64 new_pos = -1;
if (playback_speed_ < 0) {
// If we're reversing, we'll seek back by maxlen bytes before we read
qint64 len_adjusted_by_channels = maxlen / params_.channel_count();
new_pos = device_->pos() - len_adjusted_by_channels;
if (new_pos < 0) {
maxlen = device_->pos() * params_.channel_count();
new_pos = 0;
}
device_->seek(new_pos);
}
qint64 read_count = device_->read(data, maxlen);
if (playback_speed_ < 0) {
device_->seek(new_pos);
// Reverse the samples here
AudioManager::ReverseBuffer(data, static_cast<int>(read_count), params_.samples_to_bytes(1));
}
return read_count;
return -1;
}
}
+2 -8
View File
@@ -39,7 +39,7 @@ public:
void SetParameters(const AudioParams& params);
void SetDevice(QIODevice *device, qint64 offset, int playback_speed);
void SetDevice(std::shared_ptr<QIODevice> device);
virtual void close() override;
@@ -49,16 +49,10 @@ protected:
virtual qint64 writeData(const char *data, qint64 maxSize) override;
private:
qint64 ReverseAwareRead(char* data, qint64 maxlen);
QIODevice* device_;
TempoProcessor tempo_processor_;
std::shared_ptr<QIODevice> device_;
AudioParams params_;
int playback_speed_;
};
}
+2 -2
View File
@@ -90,7 +90,7 @@ void AudioOutputManager::Close()
}
}
void AudioOutputManager::PullFromDevice(QIODevice *device, qint64 offset, int playback_speed)
void AudioOutputManager::PullFromDevice(std::shared_ptr<QIODevice> device)
{
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);
device_proxy_.open(QIODevice::ReadOnly);
output_->start(&device_proxy_);
}
+3 -1
View File
@@ -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(std::shared_ptr<QIODevice> device);
// Queued
void ResetToPushMode();
@@ -86,4 +86,6 @@ private slots:
}
Q_DECLARE_METATYPE(std::shared_ptr<QIODevice>)
#endif // AUDIOHYBRIDDEVICE_H
+106
View File
@@ -0,0 +1,106 @@
/***
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 "packedprocessor.h"
#include "common/ffmpegutils.h"
namespace olive {
PackedProcessor::PackedProcessor() :
swr_ctx_(nullptr)
{
}
PackedProcessor::~PackedProcessor()
{
Close();
}
bool PackedProcessor::Open(const AudioParams &params)
{
if (IsOpen()) {
return true;
}
swr_ctx_ = swr_alloc_set_opts(nullptr,
params.channel_layout(),
FFmpegUtils::GetFFmpegSampleFormat(params.format(), false),
params.sample_rate(),
params.channel_layout(),
FFmpegUtils::GetFFmpegSampleFormat(params.format(), true),
params.sample_rate(),
0,
nullptr);
if (!swr_ctx_) {
qCritical() << "Failed to allocate resample context";
return false;
}
if (swr_init(swr_ctx_) < 0) {
qCritical() << "Failed to init resample context";
swr_free(&swr_ctx_);
return false;
}
return true;
}
QByteArray PackedProcessor::Convert(SampleBufferPtr planar)
{
if (!IsOpen()) {
qCritical() << "Tried to convert while closed";
return QByteArray();
}
int nb_samples = planar->sample_count();
if (nb_samples == 0) {
return QByteArray();
}
int nb_channels = planar->audio_params().channel_count();
QByteArray output(planar->audio_params().samples_to_bytes(nb_samples), Qt::Uninitialized);
uint8_t *output_data = reinterpret_cast<uint8_t*>(output.data());
QVector<const uint8_t*> input_arrays(nb_channels);
for (int i=0; i<nb_channels; i++) {
input_arrays[i] = reinterpret_cast<const uint8_t*>(planar->data(i));
}
int ret = swr_convert(swr_ctx_, &output_data, nb_samples, input_arrays.data(), nb_samples);
if (ret < 0) {
char buf[200];
av_strerror(ret, buf, 200);
qDebug() << "Packed processor failed with error:" << buf << ret;
}
return output;
}
void PackedProcessor::Close()
{
if (swr_ctx_) {
swr_free(&swr_ctx_);
}
}
}
+60
View File
@@ -0,0 +1,60 @@
/***
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 PACKEDPROCESSOR_H
#define PACKEDPROCESSOR_H
extern "C" {
#include <libswresample/swresample.h>
}
#include "codec/samplebuffer.h"
#include "render/audioparams.h"
namespace olive {
class PackedProcessor
{
public:
PackedProcessor();
~PackedProcessor();
DISABLE_COPY_MOVE(PackedProcessor)
bool Open(const AudioParams &params);
QByteArray Convert(SampleBufferPtr planar);
void Close();
bool IsOpen() const
{
return swr_ctx_;
}
private:
SwrContext *swr_ctx_;
};
}
#endif // PACKEDPROCESSOR_H
+4
View File
@@ -39,7 +39,11 @@ TempoProcessor::TempoProcessor() :
processed_frame_(nullptr),
open_(false)
{
}
TempoProcessor::~TempoProcessor()
{
Close();
}
bool TempoProcessor::IsOpen() const
+4
View File
@@ -42,6 +42,10 @@ class TempoProcessor
public:
TempoProcessor();
~TempoProcessor();
DISABLE_COPY_MOVE(TempoProcessor)
bool IsOpen() const;
const double& GetSpeed() const;
-23
View File
@@ -237,27 +237,4 @@ void SampleBuffer::set(int channel, const float *data, int sample_offset, int sa
memcpy(&data_[channel].data()[sample_offset], data, sizeof(float) * sample_length);
}
QByteArray SampleBuffer::toPackedData() const
{
QByteArray packed_data;
if (is_allocated()) {
packed_data.resize(audio_params_.samples_to_bytes(sample_count_per_channel_));
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++;
}
}
}
return packed_data;
}
}
-2
View File
@@ -86,8 +86,6 @@ public:
set(channel, data, 0, sample_length);
}
QByteArray toPackedData() const;
private:
AudioParams audio_params_;
+1 -1
View File
@@ -60,7 +60,7 @@ ViewerOutput::ViewerOutput(bool create_buffer_inputs, bool create_default_stream
IgnoreHashingFrom(kVideoAutoCacheInput);
IgnoreInvalidationsFrom(kVideoAutoCacheInput);
AddInput(kAudioAutoCacheInput, NodeValue::kBoolean, true, InputFlags(kInputFlagNotKeyframable | kInputFlagNotConnectable));
AddInput(kAudioAutoCacheInput, NodeValue::kBoolean, false, InputFlags(kInputFlagNotKeyframable | kInputFlagNotConnectable));
IgnoreHashingFrom(kAudioAutoCacheInput);
IgnoreInvalidationsFrom(kAudioAutoCacheInput);
}
+2
View File
@@ -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
+15 -9
View File
@@ -58,7 +58,7 @@ void AudioPlaybackCache::SetParameters(const AudioParams &params)
emit ParametersChanged();
}
void AudioPlaybackCache::WritePCM(const TimeRange &range, const TimeRangeList &valid_ranges, SampleBufferPtr samples, const AudioVisualWaveform *waveform)
void AudioPlaybackCache::WritePCM(const TimeRange &range, const TimeRangeList &valid_ranges, SampleBufferPtr samples)
{
// Ensure if we have enough segments to write this data, creating more if not
qint64 length_diff = params_.time_to_bytes_per_channel(range.out()) - playlist_.GetLength();
@@ -143,13 +143,6 @@ void AudioPlaybackCache::WritePCM(const TimeRange &range, const TimeRangeList &v
// Each segment is contiguous, so this out will be the next segment's in
this_segment_in = this_segment_out;
}
// Write visual
if (waveform) {
visual_.OverwriteSums(*waveform, r.in(), r.in() - range.in(), r.length());
} else {
visual_.OverwriteSilence(r.in(), r.length());
}
}
foreach (const TimeRange& v, ranges_we_validated) {
@@ -157,11 +150,24 @@ void AudioPlaybackCache::WritePCM(const TimeRange &range, const TimeRangeList &v
}
}
void AudioPlaybackCache::WriteWaveform(const TimeRange &range, const TimeRangeList &valid_ranges, const AudioVisualWaveform *waveform)
{
// Write each valid range to the segments
foreach (const TimeRange& r, valid_ranges) {
// Write visual
if (waveform) {
visual_.OverwriteSums(*waveform, r.in(), r.in() - range.in(), r.length());
} else {
visual_.OverwriteSilence(r.in(), r.length());
}
}
}
void AudioPlaybackCache::WriteSilence(const TimeRange &range)
{
// WritePCM will automatically fill non-existent bytes with silence, so we just have to send
// it an empty sample buffer
WritePCM(range, {range}, nullptr, nullptr);
WritePCM(range, {range}, nullptr);
}
void AudioPlaybackCache::ShiftEvent(const rational &from_in_time, const rational &to_in_time)
+3 -1
View File
@@ -66,7 +66,9 @@ public:
void SetParameters(const AudioParams& params);
void WritePCM(const TimeRange &range, const TimeRangeList &valid_ranges, SampleBufferPtr samples, const AudioVisualWaveform *waveform);
void WritePCM(const TimeRange &range, const TimeRangeList &valid_ranges, SampleBufferPtr samples);
void WriteWaveform(const TimeRange &range, const TimeRangeList &valid_ranges, const AudioVisualWaveform *waveform);
void WriteSilence(const TimeRange &range);
+111
View File
@@ -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;
}
}
+68
View File
@@ -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
+57 -15
View File
@@ -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>
@@ -12,6 +32,10 @@
namespace olive {
// We may want to make this configurable at some point, so for now this constant is used as a
// placeholder for where that configarable variable would be used.
const bool PreviewAutoCacher::kRealTimeWaveformsEnabled = true;
PreviewAutoCacher::PreviewAutoCacher() :
viewer_node_(nullptr),
use_custom_range_(false),
@@ -61,6 +85,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> &times)
{
QVector<HashData> hash_data(times.size());
@@ -101,8 +130,8 @@ void PreviewAutoCacher::VideoInvalidated(const TimeRange &range)
// want to dedicate all our rendering power to realtime feedback for the user
CancelVideoTasks();
// If a slider is not being dragged, queue up to hash these frames
if (!NodeInputDragger::IsInputBeingDragged()) {
// If auto-cache is enabled and a slider is not being dragged, queue up to hash these frames
if (viewer_node_->GetVideoAutoCacheEnabled() && !NodeInputDragger::IsInputBeingDragged()) {
invalidated_video_.insert(range);
video_job_tracker_.insert(range, graph_changed_time_);
@@ -116,7 +145,8 @@ void PreviewAutoCacher::AudioInvalidated(const TimeRange &range)
// cancelled, so some areas may end up unrendered forever
// ClearAudioQueue();
if (viewer_node_->GetAudioAutoCacheEnabled()) {
// If we're auto-caching audio or require realtime waveforms, we'll have to render this
if (viewer_node_->GetAudioAutoCacheEnabled() || kRealTimeWaveformsEnabled) {
audio_job_tracker_.insert(range, graph_changed_time_);
// Start jobs to re-render the audio at this range, split into 2 second chunks
@@ -178,11 +208,14 @@ void PreviewAutoCacher::AudioRendered()
AudioVisualWaveform waveform = watcher->GetTicket()->property("waveform").value<AudioVisualWaveform>();
// WritePCM is tolerant to its buffer being null, it will just write silence instead
viewer_node_->audio_playback_cache()->WritePCM(range,
valid_ranges,
watcher->Get().value<SampleBufferPtr>(),
&waveform);
if (viewer_node_->GetAudioAutoCacheEnabled()) {
// WritePCM is tolerant to its buffer being null, it will just write silence instead
viewer_node_->audio_playback_cache()->WritePCM(range,
valid_ranges,
watcher->Get().value<SampleBufferPtr>());
}
viewer_node_->audio_playback_cache()->WriteWaveform(range, valid_ranges, &waveform);
// Detect if this audio was incomplete because it was waiting on a conform to finish
if (watcher->GetTicket()->property("incomplete").toBool()) {
@@ -627,15 +660,12 @@ void PreviewAutoCacher::TryRender()
// Copy first range in list
TimeRange r = audio_iterator_.first();
// Limit to 1 second (FIXME: Hardcoded)
r.set_out(qMin(r.out(), r.in() + 1));
// Limit to the minimum sample rate supported by AudioVisualWaveform - we use this value so that
// whatever chunk we render can be summed down to the smallest mipmap whole
r.set_out(qMin(r.out(), r.in() + AudioVisualWaveform::kMinimumSampleRate.flipped()));
// 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 +688,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();
+26
View File
@@ -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
@@ -9,6 +29,7 @@
#include "node/node.h"
#include "node/output/viewer/viewer.h"
#include "node/project/project.h"
#include "render/audioparams.h"
#include "render/renderjobtracker.h"
#include "threading/threadticketwatcher.h"
@@ -29,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
*/
@@ -75,6 +98,7 @@ 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
@@ -168,6 +192,8 @@ private:
TimeRangeListFrameIterator hash_iterator_;
TimeRangeList audio_iterator_;
static const bool kRealTimeWaveformsEnabled;
private slots:
/**
* @brief Handler for when the NodeGraph reports a video change over a certain time range
@@ -128,8 +128,6 @@ NodeParamView::NodeParamView(QWidget *parent) :
// Set a default scale - FIXME: Hardcoded
SetScale(120);
SetMaximumScale(TimeBasedView::kMaximumScale);
// Pickup on widget focus changes
connect(qApp,
&QApplication::focusChanged,
+2 -4
View File
@@ -23,19 +23,17 @@
#include <cfloat>
#include <QtMath>
#include "audio/audiovisualwaveform.h"
#include "common/clamp.h"
namespace olive {
// Keep this aligned with the kMaximumSampleRate in AudioVisualWaveform
const double TimeScaledObject::kMaximumScale = 1024;
const int TimeScaledObject::kCalculateDimensionsPadding = 10;
TimeScaledObject::TimeScaledObject() :
scale_(1.0),
min_scale_(0),
max_scale_(kMaximumScale)
max_scale_(AudioVisualWaveform::kMaximumSampleRate.toDouble())
{
}
+1 -2
View File
@@ -44,6 +44,7 @@ public:
static rational SceneToTime(const double &x, const double& x_scale, const rational& timebase, bool round = false);
const double& GetScale() const;
const double &GetMaximumScale() const { return max_scale_; }
void SetScale(const double& scale);
@@ -54,8 +55,6 @@ public:
double TimeToScene(const rational& time) const;
rational SceneToTime(const double &x, bool round = false) const;
static const double kMaximumScale;
protected:
virtual void TimebaseChangedEvent(const rational&){}
+1 -1
View File
@@ -68,7 +68,7 @@ void ZoomTool::MouseRelease(TimelineViewMouseEvent *event)
// Normalize scale to 1.0 scale
double scene_width = (scene_right - scene_left) / parent()->GetScale();
double new_scale = qMin(TimeBasedView::kMaximumScale, static_cast<double>(reference_view->viewport()->width()) / scene_width);
double new_scale = qMin(parent()->GetFirstTimelineView()->GetMaximumScale(), static_cast<double>(reference_view->viewport()->width()) / scene_width);
parent()->SetScale(new_scale);
+187 -76
View File
@@ -31,6 +31,7 @@
#include <QVBoxLayout>
#include "audio/audiomanager.h"
#include "common/clamp.h"
#include "common/power.h"
#include "common/ratiodialog.h"
#include "common/timecodefunctions.h"
@@ -46,6 +47,7 @@ namespace olive {
#define super TimeBasedWidget
QVector<ViewerWidget*> ViewerWidget::instances_;
const int ViewerWidget::kAudioPlaybackInterval = 2;
const int kMaxPreQueueSize = 8;
@@ -54,8 +56,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 +115,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);
@@ -130,6 +129,11 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
instances_.append(this);
setAcceptDrops(true);
audio_queue_next_timer_ = new QTimer(this);
audio_queue_next_timer_->setInterval(kAudioPlaybackInterval * 1000);
audio_queue_next_timer_->setSingleShot(true);
connect(audio_queue_next_timer_, &QTimer::timeout, this, &ViewerWidget::QueueNextAudioBuffer);
}
ViewerWidget::~ViewerWidget()
@@ -182,8 +186,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();
@@ -197,6 +199,9 @@ void ViewerWidget::ConnectNodeEvent(ViewerOutput *n)
last_length_ = 0;
LengthChangedSlot(n->GetLength());
AudioParams ap = n->GetAudioParams();
packed_processor_.Open(ap);
ColorManager* color_manager = n->project()->color_manager();
display_widget_->ConnectColorManager(color_manager);
@@ -228,10 +233,10 @@ 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);
packed_processor_.Close();
SetDisplayImage(QVariant());
ruler()->SetPlaybackCache(nullptr);
@@ -248,7 +253,7 @@ void ViewerWidget::DisconnectNodeEvent(ViewerOutput *n)
waveform_view_->ConnectTimelinePoints(nullptr);
// Queue an UpdateStack so that when it runs, the viewer node will be fully disconnected
QMetaObject::invokeMethod(this, "UpdateStack", Qt::QueuedConnection);
QMetaObject::invokeMethod(this, &ViewerWidget::UpdateStack, Qt::QueuedConnection);
}
void ViewerWidget::ConnectedNodeChangeEvent(ViewerOutput *n)
@@ -408,17 +413,108 @@ 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(),
AudioParams params = GetConnectedNode()->GetAudioParams();
if (params.is_valid()) {
AudioManager::instance()->SetOutputParams(params);
AudioManager::instance()->StartOutput(audio_playback_device_);
emit AudioManager::instance()->OutputWaveformStarted(&GetConnectedNode()->audio_playback_cache()->visual(),
GetTime(), playback_speed_);
}
}
void ViewerWidget::QueueNextAudioBuffer()
{
// NOTE: Hardcoded 2 second interval
rational queue_end = audio_playback_queue_time_ + (kAudioPlaybackInterval * playback_speed_);
// Clamp queue end by zero and the audio length
queue_end = clamp(queue_end, rational(0), GetConnectedNode()->GetAudioLength());
if (queue_end == audio_playback_queue_time_) {
// This will queue nothing, so stop the loop here
return;
}
RenderTicketWatcher *watcher = new RenderTicketWatcher(this);
connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::ReceivedAudioBufferForPlayback);
audio_playback_queue_.push_back(watcher);
watcher->SetTicket(auto_cacher_.GetRangeOfAudio(TimeRange(audio_playback_queue_time_, queue_end), true));
audio_playback_queue_time_ = queue_end;
}
void ViewerWidget::ReceivedAudioBufferForPlayback()
{
while (!audio_playback_queue_.empty() && audio_playback_queue_.front()->HasResult()) {
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_) {
// If the samples must be reversed, reverse them now
if (playback_speed_ < 0) {
samples->reverse();
}
// Convert to packed data for audio output
QByteArray pack = packed_processor_.Convert(samples);
// If the tempo must be adjusted, adjust now
if (tempo_processor_.IsOpen()) {
tempo_processor_.Push(pack.data(), pack.size());
int actual = tempo_processor_.Pull(pack.data(), pack.size());
if (actual != pack.size()) {
pack.resize(actual);
}
}
// TempoProcessor may have emptied the array
if (!pack.isEmpty()) {
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
audio_queue_next_timer_->stop();
audio_queue_next_timer_->start();
delete watcher;
}
}
void ViewerWidget::ReceivedAudioBufferForScrubbing()
{
// NOTE: Might be good to organize a queue for this in the event that audio takes a long time to
// keep the scrubbed chunks ordered, similar to the playback_queue_ or audio_playback_queue_
RenderTicketWatcher *watcher = static_cast<RenderTicketWatcher *>(sender());
if (watcher->HasResult()) {
if (SampleBufferPtr samples = watcher->Get().value<SampleBufferPtr>()) {
/* Fade code
const int kFadeSz = qMin(200, samples->sample_count()/4);
for (int i=0; i<kFadeSz; i++) {
float amt = float(i)/float(kFadeSz);
samples->transform_volume_for_sample(i, amt);
samples->transform_volume_for_sample(samples->sample_count() - i - 1, amt);
}*/
AudioManager::instance()->SetOutputParams(samples->audio_params());
AudioManager::instance()->PushToOutput(packed_processor_.Convert(samples));
}
}
delete watcher;
}
void ViewerWidget::UpdateTextureFromNode()
{
rational time = GetTime();
@@ -475,7 +571,7 @@ void ViewerWidget::UpdateTextureFromNode()
// Only show warning if frame actually exists
if (frame_exists_at_time && !frame_might_be_still) {
qWarning() << "Playback queue failed to keep up";
//qWarning() << "Playback queue failed to keep up";
}
}
@@ -551,7 +647,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,8 +664,14 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only)
}
}
if (!prequeuing_) {
FinishPlayPreprocess();
if (std::abs(playback_speed_) > 1) {
tempo_processor_.Open(GetConnectedNode()->GetAudioParams(), std::abs(playback_speed_));
}
audio_playback_device_ = std::make_shared<PreviewAudioDevice>();
prequeuing_audio_ = true;
audio_playback_queue_time_ = GetTime();
for (int i=0; i<2; i++) {
QueueNextAudioBuffer();
}
}
@@ -588,37 +690,35 @@ void ViewerWidget::PauseInternal()
playback_queue_.clear();
playback_backup_timer_.stop();
audio_restart_timer_.stop();
audio_playback_device_ = nullptr;
qDeleteAll(audio_playback_queue_);
audio_playback_queue_.clear();
if (tempo_processor_.IsOpen()) {
tempo_processor_.Close();
}
audio_queue_next_timer_->stop();
UpdateTextureFromNode();
}
prequeuing_ = false;
prequeuing_video_ = false;
prequeuing_audio_ = false;
}
void ViewerWidget::PushScrubbedAudio()
{
if (!IsPlaying() && GetConnectedNode() && Config::Current()["AudioScrubbing"].toBool()) {
if (!IsPlaying() && GetConnectedNode() && Config::Current()[QStringLiteral("AudioScrubbing")].toBool()) {
// Get audio src device from renderer
const AudioParams& params = GetConnectedNode()->audio_playback_cache()->GetParameters();
if (params.is_valid()) {
AudioPlaybackCache::PlaybackDevice* audio_src = GetConnectedNode()->audio_playback_cache()->CreatePlaybackDevice();
// NOTE: Hardcoded scrubbing interval (20ms)
rational interval = rational(50, 1000);
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);
audio_src->close();
}
delete audio_src;
RenderTicketWatcher *watcher = new RenderTicketWatcher();
connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::ReceivedAudioBufferForScrubbing);
watcher->SetTicket(auto_cacher_.GetRangeOfAudio(TimeRange(GetTime(), GetTime() + interval), true));
}
}
}
@@ -714,6 +814,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 +963,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 +972,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();
}
}
@@ -1154,6 +1259,10 @@ void ViewerWidget::PlaybackTimerUpdate()
max_time = qMax(min_time, max_time - timebase());
}
rational time_to_set;
bool end_of_line = false;
bool play_after_pause = false;
if ((playback_speed_ < 0 && current_time <= min_time)
|| (playback_speed_ > 0 && current_time >= max_time)) {
@@ -1166,34 +1275,48 @@ void ViewerWidget::PlaybackTimerUpdate()
tripped_time = max_time;
}
// Signal that we've reached the end of whatever range we're playing and should either pause
// or restart playback
end_of_line = true;
if (Config::Current()[QStringLiteral("Loop")].toBool()) {
// If we're looping, jump to the other side of the workarea and continue
rational opposing_time = (tripped_time == min_time) ? max_time : min_time;
time_to_set = (tripped_time == min_time) ? max_time : min_time;
// Cache the current speed
int current_speed = playback_speed_;
// Jump to the other side and keep playing at the same speed
SetTimeAndSignal(opposing_time);
PlayInternal(current_speed, play_in_to_out_only_);
// Signal to restart playback after the pause signalled by `end_of_line`
play_after_pause = true;
} else {
// Pause at the boundary
SetTimeAndSignal(tripped_time);
// Pause at the boundary we tripped
time_to_set = tripped_time;
}
} else {
// Sets time, wrapping in this bool ensures we don't pause from setting the time
time_changed_from_timer_ = true;
SetTimeAndSignal(current_time);
time_changed_from_timer_ = false;
// Sets time normally to whatever we calculated as the "current time"
time_to_set = current_time;
}
// Set the time. By wrapping in this bool, we prevent TimeChangedEvent's default behavior of
// pausing. Even if we pause it later with `end_of_line`, we prefer pausing after setting the time
// so that an audio scrub event, etc. isn't sent.
time_changed_from_timer_ = true;
SetTimeAndSignal(time_to_set);
time_changed_from_timer_ = false;
if (end_of_line) {
// Cache the current speed
int current_speed = playback_speed_;
PauseInternal();
if (play_after_pause) {
PlayInternal(current_speed, play_in_to_out_only_);
}
}
if (display_widget_->isVisible()) {
// Updating display widget
UpdateTextureFromNode();
@@ -1259,6 +1382,11 @@ void ViewerWidget::UpdateRendererVideoParameters()
void ViewerWidget::UpdateRendererAudioParameters()
{
packed_processor_.Close();
AudioParams ap = GetConnectedNode()->GetAudioParams();
packed_processor_.Open(ap);
}
void ViewerWidget::SetZoomFromMenu(QAction *action)
@@ -1270,7 +1398,7 @@ void ViewerWidget::ViewerInvalidatedVideoRange(const TimeRange &range)
{
// If our current frame is within this range, we need to update
if (GetTime() >= range.in() && (GetTime() < range.out() || range.in() == range.out())) {
QMetaObject::invokeMethod(this, "UpdateTextureFromNode", Qt::QueuedConnection);
QMetaObject::invokeMethod(this, &ViewerWidget::UpdateTextureFromNode, Qt::QueuedConnection);
}
}
@@ -1286,7 +1414,7 @@ void ViewerWidget::ManualSwitchToWaveform(bool e)
void ViewerWidget::ViewerShiftedRange(const rational &from, const rational &to)
{
if (GetTime() >= qMin(from, to)) {
QMetaObject::invokeMethod(this, "UpdateTextureFromNode", Qt::QueuedConnection);
QMetaObject::invokeMethod(this, &ViewerWidget::UpdateTextureFromNode, Qt::QueuedConnection);
}
}
@@ -1323,21 +1451,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();
}
}
}
+19 -6
View File
@@ -28,10 +28,13 @@
#include <QTimer>
#include <QWidget>
#include "audio/packedprocessor.h"
#include "audio/tempoprocessor.h"
#include "audiowaveformview.h"
#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 +243,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 +254,16 @@ private:
PreviewAutoCacher auto_cacher_;
QTimer audio_restart_timer_;
int active_queue_jobs_;
std::shared_ptr<PreviewAudioDevice> audio_playback_device_;
std::list<RenderTicketWatcher*> audio_playback_queue_;
rational audio_playback_queue_time_;
PackedProcessor packed_processor_;
TempoProcessor tempo_processor_;
static const int kAudioPlaybackInterval;
QTimer *audio_queue_next_timer_;
static QVector<ViewerWidget*> instances_;
private slots:
@@ -299,11 +309,14 @@ private slots:
void Dropped(QDropEvent* event);
void AudioCacheInvalidated();
void AudioCacheValidated();
void StartAudioOutput();
void QueueNextAudioBuffer();
void ReceivedAudioBufferForPlayback();
void ReceivedAudioBufferForScrubbing();
};
}