diff --git a/app/codec/CMakeLists.txt b/app/codec/CMakeLists.txt index 42f456059..fa6e81c25 100644 --- a/app/codec/CMakeLists.txt +++ b/app/codec/CMakeLists.txt @@ -31,6 +31,8 @@ set(OLIVE_SOURCES codec/exportformat.h codec/frame.cpp codec/frame.h + codec/planarfiledevice.cpp + codec/planarfiledevice.h codec/samplebuffer.cpp codec/samplebuffer.h PARENT_SCOPE diff --git a/app/codec/conformmanager.cpp b/app/codec/conformmanager.cpp index 340f1e763..70d012a94 100644 --- a/app/codec/conformmanager.cpp +++ b/app/codec/conformmanager.cpp @@ -14,9 +14,9 @@ ConformManager::Conform ConformManager::GetConformState(const QString &decoder_i QMutexLocker locker(&mutex_); // Return existing conform if exists - QString filename = GetConformedFilename(cache_path, stream, params); - if (QFileInfo::exists(filename)) { - return {kConformExists, filename, nullptr}; + QVector filenames = GetConformedFilename(cache_path, stream, params); + if (AllConformsExist(filenames)) { + return {kConformExists, filenames, nullptr}; } ConformTask *conforming_task = nullptr; @@ -34,36 +34,56 @@ ConformManager::Conform ConformManager::GetConformState(const QString &decoder_i // We conform to a different filename until it's done to make it clear even across sessions // whether this conform is ready or not - QString working_fn = filename; - working_fn.append(QStringLiteral(".working")); + QVector working_filenames = filenames; + for (int i=0; imoveToThread(TaskManager::instance()->thread()); QMetaObject::invokeMethod(TaskManager::instance(), "AddTask", Qt::QueuedConnection, Q_ARG(Task *, conforming_task)); - conforming_.append({stream, params, conforming_task, working_fn, filename}); + conforming_.append({stream, params, conforming_task, working_filenames, filenames}); } if (wait) { do { conform_done_condition_.wait(&mutex_); - } while (!QFileInfo::exists(filename)); - return {kConformExists, filename, nullptr}; + } while (!AllConformsExist(filenames)); + return {kConformExists, filenames, nullptr}; } - return {kConformGenerating, QString(), conforming_task}; + return {kConformGenerating, QVector(), conforming_task}; } -QString ConformManager::GetConformedFilename(const QString &cache_path, const Decoder::CodecStream &stream, const AudioParams ¶ms) +QVector ConformManager::GetConformedFilename(const QString &cache_path, const Decoder::CodecStream &stream, const AudioParams ¶ms) { - QString index_fn = QStringLiteral("%1-%2.%3.%4.%5.pcm").arg(FileFunctions::GetUniqueFileIdentifier(stream.filename()), - QString::number(stream.stream()), - QString::number(params.sample_rate()), - QString::number(params.format()), - QString::number(params.channel_layout())); + QVector filenames(params.channel_count()); - return QDir(cache_path).filePath(index_fn); + for (int i=0; i &filenames) +{ + foreach (const QString &fn, filenames) { + if (!QFileInfo::exists(fn)) { + return false; + } + } + + return true; } void ConformManager::ConformTaskFinished(Task *task, bool succeeded) @@ -84,15 +104,22 @@ void ConformManager::ConformTaskFinished(Task *task, bool succeeded) if (succeeded) { // Move file to standard conform name, making it clear this conform is ready for use - QFile::remove(data.finished_filename); - QFile::rename(data.working_filename, data.finished_filename); + for (int i=0; i filenames; ConformTask *task; }; @@ -65,8 +65,8 @@ private: Decoder::CodecStream stream; AudioParams params; ConformTask *task; - QString working_filename; - QString finished_filename; + QVector working_filename; + QVector finished_filename; }; QVector conforming_; @@ -74,7 +74,9 @@ private: /** * @brief Get the destination filename of an audio stream conformed to a set of parameters */ - static QString GetConformedFilename(const QString &cache_path, const Decoder::CodecStream &stream, const AudioParams ¶ms); + static QVector GetConformedFilename(const QString &cache_path, const Decoder::CodecStream &stream, const AudioParams ¶ms); + + static bool AllConformsExist(const QVector &filenames); private slots: void ConformTaskFinished(Task *task, bool succeeded); diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index 2b8aee6db..7d33a0e3f 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -24,6 +24,7 @@ #include #include "codec/ffmpeg/ffmpegdecoder.h" +#include "codec/planarfiledevice.h" #include "codec/oiio/oiiodecoder.h" #include "common/ffmpegutils.h" #include "common/filefunctions.h" @@ -131,7 +132,7 @@ Decoder::RetrieveAudioData Decoder::RetrieveAudio(const TimeRange &range, const } // See if we got the conform - SampleBufferPtr out_buffer = RetrieveAudioFromConform(conform.filename, range, loop_mode, params); + SampleBufferPtr out_buffer = RetrieveAudioFromConform(conform.filenames, range, loop_mode, params); return {kOK, out_buffer, nullptr}; } @@ -156,9 +157,9 @@ void Decoder::Close() } } -bool Decoder::ConformAudio(const QString &output_filename, const AudioParams ¶ms, const QAtomicInt *cancelled) +bool Decoder::ConformAudio(const QVector &output_filenames, const AudioParams ¶ms, const QAtomicInt *cancelled) { - return ConformAudioInternal(output_filename, params, cancelled); + return ConformAudioInternal(output_filenames, params, cancelled); } /* @@ -260,25 +261,26 @@ FramePtr Decoder::RetrieveVideoInternal(const rational &timecode, const Retrieve return nullptr; } -bool Decoder::ConformAudioInternal(const QString& filename, const AudioParams ¶ms, const QAtomicInt* cancelled) +bool Decoder::ConformAudioInternal(const QVector &filenames, const AudioParams ¶ms, const QAtomicInt* cancelled) { - Q_UNUSED(filename) + Q_UNUSED(filenames) Q_UNUSED(cancelled) Q_UNUSED(params) return false; } -SampleBufferPtr Decoder::RetrieveAudioFromConform(const QString &conform_filename, const TimeRange& range, Footage::LoopMode loop_mode, const AudioParams &input_params) +SampleBufferPtr Decoder::RetrieveAudioFromConform(const QVector &conform_filenames, const TimeRange& range, Footage::LoopMode loop_mode, const AudioParams &input_params) { - QFile input(conform_filename); + PlanarFileDevice input; + if (input.open(conform_filenames, QFile::ReadOnly)) { + SampleBufferPtr sample_buffer = SampleBuffer::CreateAllocated(input_params, range.length()); - if (input.open(QFile::ReadOnly)) { - QByteArray packed_data(input_params.time_to_bytes(range.length()), Qt::Uninitialized); - - qint64 read_index = input_params.time_to_bytes(range.in()); + qint64 read_index = input_params.time_to_bytes(range.in()) / input_params.channel_count(); qint64 write_index = 0; - while (write_index < packed_data.size()) { + const qint64 buffer_length_in_bytes = sample_buffer->sample_count() * input_params.bytes_per_sample_per_channel(); + + while (write_index < buffer_length_in_bytes) { if (loop_mode == Footage::kLoopModeLoop) { while (read_index >= input.size()) { read_index -= input.size(); @@ -293,16 +295,16 @@ SampleBufferPtr Decoder::RetrieveAudioFromConform(const QString &conform_filenam if (read_index < 0) { // Reading before 0, write silence here until audio data would actually start - write_count = qMin(-read_index, qint64(packed_data.size())); - memset(packed_data.data() + write_index, 0, write_count); + write_count = qMin(-read_index, buffer_length_in_bytes); + sample_buffer->silence_bytes(write_index, write_index + write_count); } else if (read_index >= input.size()) { // Reading after data length, write silence until the end of the buffer - write_count = packed_data.size() - write_index; - memset(packed_data.data() + write_index, 0, write_count); + write_count = buffer_length_in_bytes - write_index; + sample_buffer->silence_bytes(write_index, write_index + write_count); } else { - write_count = qMin(input.size() - read_index, packed_data.size() - write_index); + write_count = qMin(input.size() - read_index, buffer_length_in_bytes - write_index); input.seek(read_index); - input.read(packed_data.data() + write_index, write_count); + input.read(reinterpret_cast(sample_buffer->to_raw_ptrs()), write_count, write_index); } read_index += write_count; @@ -311,9 +313,6 @@ SampleBufferPtr Decoder::RetrieveAudioFromConform(const QString &conform_filenam input.close(); - // Create sample buffer - SampleBufferPtr sample_buffer = SampleBuffer::CreateFromPackedData(input_params, packed_data); - return sample_buffer; } diff --git a/app/codec/decoder.h b/app/codec/decoder.h index 0de9b4f52..0e30ce455 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -236,7 +236,7 @@ public: /** * @brief Conform audio stream */ - bool ConformAudio(const QString &output_filename, const AudioParams ¶ms, const QAtomicInt *cancelled = nullptr); + bool ConformAudio(const QVector &output_filenames, const AudioParams ¶ms, const QAtomicInt *cancelled = nullptr); /** * @brief Create a Decoder instance using a Decoder ID @@ -286,7 +286,7 @@ protected: */ virtual FramePtr RetrieveVideoInternal(const rational& timecode, const RetrieveVideoParams& divider, const QAtomicInt *cancelled); - virtual bool ConformAudioInternal(const QString& filename, const AudioParams ¶ms, const QAtomicInt* cancelled); + virtual bool ConformAudioInternal(const QVector& filenames, const AudioParams ¶ms, const QAtomicInt* cancelled); void SignalProcessingProgress(int64_t ts, int64_t duration); @@ -312,7 +312,7 @@ signals: private: void UpdateLastAccessed(); - SampleBufferPtr RetrieveAudioFromConform(const QString& conform_filename, const TimeRange &range, Footage::LoopMode loop_mode, const AudioParams ¶ms); + SampleBufferPtr RetrieveAudioFromConform(const QVector &conform_filenames, const TimeRange &range, Footage::LoopMode loop_mode, const AudioParams ¶ms); CodecStream stream_; diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 84a4a3576..fba94088d 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -38,6 +38,7 @@ extern "C" { #include #include +#include "codec/planarfiledevice.h" #include "common/define.h" #include "common/ffmpegutils.h" #include "common/filefunctions.h" @@ -437,7 +438,7 @@ QString FFmpegDecoder::FFmpegError(int error_code) return QStringLiteral("%1 %2").arg(QString::number(error_code), err); } -bool FFmpegDecoder::ConformAudioInternal(const QString &filename, const AudioParams ¶ms, const QAtomicInt *cancelled) +bool FFmpegDecoder::ConformAudioInternal(const QVector &filenames, const AudioParams ¶ms, const QAtomicInt *cancelled) { // Iterate through each audio frame and extract the PCM data @@ -454,7 +455,7 @@ bool FFmpegDecoder::ConformAudioInternal(const QString &filename, const AudioPar // Create resampling context SwrContext* resampler = swr_alloc_set_opts(nullptr, params.channel_layout(), - FFmpegUtils::GetFFmpegSampleFormat(params.format()), + FFmpegUtils::GetFFmpegSampleFormat(params.format(), true), params.sample_rate(), channel_layout, static_cast(instance_.avstream()->codecpar->format), @@ -464,8 +465,6 @@ bool FFmpegDecoder::ConformAudioInternal(const QString &filename, const AudioPar swr_init(resampler); - QFile wave_out(filename); - AVPacket* pkt = av_packet_alloc(); AVFrame* frame = av_frame_alloc(); int ret; @@ -481,7 +480,12 @@ bool FFmpegDecoder::ConformAudioInternal(const QString &filename, const AudioPar } } - if (wave_out.open(QFile::WriteOnly)) { + PlanarFileDevice wave_out; + if (wave_out.open(filenames, QFile::WriteOnly)) { + int nb_channels = params.channel_count(); + SampleBuffer data; + data.set_audio_params(params); + while (true) { // Check if we have a `cancelled` ptr and its value if (cancelled && *cancelled) { @@ -505,15 +509,30 @@ bool FFmpegDecoder::ConformAudioInternal(const QString &filename, const AudioPar // Allocate buffers int nb_samples = swr_get_out_samples(resampler, frame->nb_samples); - char* data = new char[params.samples_to_bytes(nb_samples)]; + int nb_bytes_per_channel = params.samples_to_bytes(nb_samples) / nb_channels; + data.set_sample_count(nb_bytes_per_channel); + data.allocate(); // Resample audio to our destination parameters nb_samples = swr_convert(resampler, - reinterpret_cast(&data), + reinterpret_cast(data.to_raw_ptrs()), nb_samples, const_cast(frame->data), frame->nb_samples); + // If no error, write to files + if (nb_samples > 0) { + // Update byte count for the number of samples we actually received + nb_bytes_per_channel = params.samples_to_bytes(nb_samples) / nb_channels; + + // Write to files + wave_out.write(const_cast(reinterpret_cast(data.to_raw_ptrs())), nb_bytes_per_channel); + } + + // Free buffer + data.destroy(); + + // Handle error now after freeing if (nb_samples < 0) { char err_str[512]; av_strerror(nb_samples, err_str, 512); @@ -521,14 +540,6 @@ bool FFmpegDecoder::ConformAudioInternal(const QString &filename, const AudioPar break; } - // Write packed WAV data to the disk cache - wave_out.write(data, params.samples_to_bytes(nb_samples)); - - // If we allocated an output for the resampler, delete it here - if (data != reinterpret_cast(frame->data[0])) { - delete [] data; - } - SignalProcessingProgress(frame->pts, duration); } diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index be06f9a60..f3590b212 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -64,7 +64,7 @@ public: protected: virtual bool OpenInternal() override; virtual FramePtr RetrieveVideoInternal(const rational &timecode, const RetrieveVideoParams& params, const QAtomicInt *cancelled) override; - virtual bool ConformAudioInternal(const QString& filename, const AudioParams ¶ms, const QAtomicInt* cancelled) override; + virtual bool ConformAudioInternal(const QVector& filenames, const AudioParams ¶ms, const QAtomicInt* cancelled) override; virtual void CloseInternal() override; private: diff --git a/app/codec/planarfiledevice.cpp b/app/codec/planarfiledevice.cpp new file mode 100644 index 000000000..c68d3ba8c --- /dev/null +++ b/app/codec/planarfiledevice.cpp @@ -0,0 +1,119 @@ +/*** + + 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 "planarfiledevice.h" + +namespace olive { + +PlanarFileDevice::PlanarFileDevice(QObject *parent) : + QObject(parent) +{ + +} + +PlanarFileDevice::~PlanarFileDevice() +{ + close(); +} + +bool PlanarFileDevice::open(const QVector &filenames, QIODevice::OpenMode mode) +{ + if (isOpen()) { + // Already open + return false; + } + + files_.resize(filenames.size()); + files_.fill(nullptr); + + for (int i=0; iopen(mode)) { + close(); + return false; + } + } + + return true; +} + +qint64 PlanarFileDevice::read(char **data, qint64 bytes_per_channel, qint64 offset) +{ + qint64 ret = -1; + + if (isOpen()) { + for (int i=0; iread(data[i] + offset, bytes_per_channel); + } + } + + return ret; +} + +qint64 PlanarFileDevice::write(const char **data, qint64 bytes_per_channel, qint64 offset) +{ + qint64 ret = -1; + + if (isOpen()) { + for (int i=0; iwrite(data[i] + offset, bytes_per_channel); + } + } + + return ret; +} + +qint64 PlanarFileDevice::size() const +{ + if (isOpen()) { + return files_.first()->size(); + } else { + return 0; + } +} + +bool PlanarFileDevice::seek(qint64 pos) +{ + bool ret = true; + + for (int i=0; iseek(pos) & ret; + } + + return ret; +} + +void PlanarFileDevice::close() +{ + for (int i=0; iisOpen()) { + f->close(); + } + delete f; + } + } + files_.clear(); +} + +} diff --git a/app/codec/planarfiledevice.h b/app/codec/planarfiledevice.h new file mode 100644 index 000000000..d02dafa19 --- /dev/null +++ b/app/codec/planarfiledevice.h @@ -0,0 +1,64 @@ +/*** + + 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 PLANARFILEDEVICE_H +#define PLANARFILEDEVICE_H + +#include +#include + +#include "codec/samplebuffer.h" +#include "common/define.h" + +namespace olive { + +class PlanarFileDevice : public QObject +{ + Q_OBJECT +public: + PlanarFileDevice(QObject *parent = nullptr); + + virtual ~PlanarFileDevice() override; + + bool isOpen() const + { + return !files_.isEmpty(); + } + + bool open(const QVector &filenames, QIODevice::OpenMode mode); + + qint64 read(char **data, qint64 bytes_per_channel, qint64 offset = 0); + + qint64 write(const char **data, qint64 bytes_per_channel, qint64 offset = 0); + + qint64 size() const; + + bool seek(qint64 pos); + + void close(); + +private: + QVector files_; + +}; + +} + +#endif // PLANARFILEDEVICE_H diff --git a/app/codec/samplebuffer.cpp b/app/codec/samplebuffer.cpp index 1753e80c1..1dede4ae5 100644 --- a/app/codec/samplebuffer.cpp +++ b/app/codec/samplebuffer.cpp @@ -48,30 +48,6 @@ SampleBufferPtr SampleBuffer::CreateAllocated(const AudioParams &audio_params, i return buffer; } -SampleBufferPtr SampleBuffer::CreateFromPackedData(const AudioParams &audio_params, const QByteArray &bytes) -{ - if (!audio_params.is_valid()) { - qWarning() << "Tried to create from packed data with invalid parameters"; - return nullptr; - } - - int samples_per_channel = audio_params.bytes_to_samples(bytes.size()); - SampleBufferPtr buffer = CreateAllocated(audio_params, samples_per_channel); - - int total_samples = samples_per_channel * audio_params.channel_count(); - - const float* packed_data = reinterpret_cast(bytes.constData()); - - for (int i=0;idata_[channel][index] = packed_data[i]; - } - - return buffer; -} - const AudioParams &SampleBuffer::audio_params() const { return audio_params_; @@ -128,11 +104,14 @@ void SampleBuffer::allocate() for (int i=0; i(data_[i].data()) + start_byte, 0, end_byte - start_byte); } } @@ -235,4 +220,12 @@ void SampleBuffer::set(int channel, const float *data, int sample_offset, int sa memcpy(&data_[channel].data()[sample_offset], data, sizeof(float) * sample_length); } +void SampleBuffer::update_raw() +{ + raw_ptrs_.resize(data_.size()); + for (int i=0; i > data_; + QVector raw_ptrs_; }; diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 86cfaa384..03a94efd9 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -41,6 +41,7 @@ const bool PreviewAutoCacher::kRealTimeWaveformsEnabled = true; PreviewAutoCacher::PreviewAutoCacher() : viewer_node_(nullptr), use_custom_range_(false), + pause_audio_(false), single_frame_render_(nullptr) { // Set defaults @@ -532,6 +533,14 @@ void PreviewAutoCacher::CancelAudioTasks(bool and_wait_for_them_to_finish) CancelTasks(audio_tasks_, and_wait_for_them_to_finish); } +void PreviewAutoCacher::SetAudioPaused(bool e) +{ + pause_audio_ = e; + if (!e) { + TryRender(); + } +} + void PreviewAutoCacher::NodeAdded(Node *node) { graph_update_queue_.append({QueuedJob::kNodeAdded, node, NodeInput(), nullptr}); @@ -672,7 +681,7 @@ void PreviewAutoCacher::TryRender() } // Handle audio tasks - while (!audio_iterator_.isEmpty() && audio_tasks_.size() < max_tasks) { + while (!audio_iterator_.isEmpty() && audio_tasks_.size() < max_tasks && !pause_audio_) { // Copy first range in list TimeRange r = audio_iterator_.first(); diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h index 246b0d1ff..ad4665da3 100644 --- a/app/render/previewautocacher.h +++ b/app/render/previewautocacher.h @@ -99,6 +99,8 @@ public: return queued_frame_iterator_.IsCustomRange() && queued_frame_iterator_.HasNext(); } + void SetAudioPaused(bool e); + signals: void StopCacheProxyTasks(); @@ -182,6 +184,8 @@ private: TimeRangeList invalidated_video_; TimeRangeList invalidated_audio_; + bool pause_audio_; + RenderTicketPtr single_frame_render_; QList >*> hash_tasks_; diff --git a/app/task/conform/conform.cpp b/app/task/conform/conform.cpp index 44c0aa5a6..4ee1ea5f9 100644 --- a/app/task/conform/conform.cpp +++ b/app/task/conform/conform.cpp @@ -22,11 +22,11 @@ namespace olive { -ConformTask::ConformTask(const QString &decoder_id, const Decoder::CodecStream &stream, const AudioParams& params, const QString &output_filename) : +ConformTask::ConformTask(const QString &decoder_id, const Decoder::CodecStream &stream, const AudioParams& params, const QVector &output_filenames) : decoder_id_(decoder_id), stream_(stream), params_(params), - output_filename_(output_filename) + output_filenames_(output_filenames) { SetTitle(tr("Conforming Audio %1:%2").arg(stream.filename(), QString::number(stream.stream()))); } @@ -42,7 +42,7 @@ bool ConformTask::Run() connect(decoder.get(), &Decoder::IndexProgress, this, &ConformTask::ProgressChanged); - bool ret = decoder->ConformAudio(output_filename_, params_, &IsCancelled()); + bool ret = decoder->ConformAudio(output_filenames_, params_, &IsCancelled()); decoder->Close(); diff --git a/app/task/conform/conform.h b/app/task/conform/conform.h index 64f998ac5..32f480048 100644 --- a/app/task/conform/conform.h +++ b/app/task/conform/conform.h @@ -32,7 +32,7 @@ class ConformTask : public Task { Q_OBJECT public: - ConformTask(const QString &decoder_id, const Decoder::CodecStream &stream, const AudioParams& params, const QString &output_filename); + ConformTask(const QString &decoder_id, const Decoder::CodecStream &stream, const AudioParams& params, const QVector &output_filenames); protected: virtual bool Run() override; @@ -44,7 +44,7 @@ private: AudioParams params_; - QString output_filename_; + QVector output_filenames_; }; diff --git a/app/widget/viewer/audiowaveformview.cpp b/app/widget/viewer/audiowaveformview.cpp index fdc52edc4..198cff332 100644 --- a/app/widget/viewer/audiowaveformview.cpp +++ b/app/widget/viewer/audiowaveformview.cpp @@ -40,20 +40,6 @@ AudioWaveformView::AudioWaveformView(QWidget *parent) : setBackgroundRole(QPalette::Base); } -AudioVisualWaveform GenerateWaveform(QIODevice* device, AudioParams params, TimeRange range) -{ - device->open(QFile::ReadOnly); - device->seek(params.time_to_bytes(range.in())); - - SampleBufferPtr samples = SampleBuffer::CreateFromPackedData(params, device->read(params.time_to_bytes(range.length()))); - AudioVisualWaveform waveform; - waveform.set_channel_count(params.channel_count()); - waveform.OverwriteSamples(samples, params.sample_rate()); - device->close(); - delete device; - return waveform; -} - void AudioWaveformView::SetViewer(AudioPlaybackCache *playback) { if (playback_) { diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 627613e40..7054fab40 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -634,6 +634,8 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) viewer->PauseInternal(); viewer->ClearVideoAutoCacherQueue(); } + + viewer->auto_cacher_.SetAudioPaused(true); } // If the playhead is beyond the end, restart at 0 @@ -722,6 +724,10 @@ void ViewerWidget::PauseInternal() tempo_processor_.Close(); } + foreach (ViewerWidget* viewer, instances_) { + viewer->auto_cacher_.SetAudioPaused(false); + } + UpdateTextureFromNode(); }