Merge branch 'master' into pr/1770
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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<QString> 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<QString> working_filenames = filenames;
|
||||
for (int i=0; i<working_filenames.size(); i++) {
|
||||
working_filenames[i].append(QStringLiteral(".working"));
|
||||
}
|
||||
|
||||
conforming_task = new ConformTask(decoder_id, stream, params, working_fn);
|
||||
conforming_task = new ConformTask(decoder_id, stream, params, working_filenames);
|
||||
connect(conforming_task, &ConformTask::Finished, this, &ConformManager::ConformTaskFinished);
|
||||
conforming_task->moveToThread(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<QString>(), conforming_task};
|
||||
}
|
||||
|
||||
QString ConformManager::GetConformedFilename(const QString &cache_path, const Decoder::CodecStream &stream, const AudioParams ¶ms)
|
||||
QVector<QString> 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<QString> filenames(params.channel_count());
|
||||
|
||||
return QDir(cache_path).filePath(index_fn);
|
||||
for (int i=0; i<filenames.size(); i++) {
|
||||
QString index_fn = QStringLiteral("%1-%2.%3.%4.%5.%6.pcm").arg(FileFunctions::GetUniqueFileIdentifier(stream.filename()),
|
||||
QString::number(stream.stream()),
|
||||
QString::number(params.sample_rate()),
|
||||
QString::number(params.format()),
|
||||
QString::number(params.channel_layout()),
|
||||
QString::number(i));
|
||||
|
||||
filenames[i] = QDir(cache_path).filePath(index_fn);
|
||||
}
|
||||
|
||||
return filenames;
|
||||
}
|
||||
|
||||
bool ConformManager::AllConformsExist(const QVector<QString> &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<data.finished_filename.size(); i++) {
|
||||
const QString &finished = data.finished_filename.at(i);
|
||||
const QString &working = data.working_filename.at(i);
|
||||
|
||||
QFile::remove(finished);
|
||||
QFile::rename(working, finished);
|
||||
}
|
||||
|
||||
conform_done_condition_.wakeAll();
|
||||
locker.unlock();
|
||||
emit ConformReady();
|
||||
} else {
|
||||
// Failed, just delete the working filename if exists
|
||||
QFile::remove(data.working_filename);
|
||||
for (int i=0; i<data.working_filename.size(); i++) {
|
||||
QFile::remove(data.working_filename.at(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ public:
|
||||
|
||||
struct Conform {
|
||||
ConformState state;
|
||||
QString filename;
|
||||
QVector<QString> filenames;
|
||||
ConformTask *task;
|
||||
};
|
||||
|
||||
@@ -65,8 +65,8 @@ private:
|
||||
Decoder::CodecStream stream;
|
||||
AudioParams params;
|
||||
ConformTask *task;
|
||||
QString working_filename;
|
||||
QString finished_filename;
|
||||
QVector<QString> working_filename;
|
||||
QVector<QString> finished_filename;
|
||||
};
|
||||
|
||||
QVector<ConformData> 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<QString> GetConformedFilename(const QString &cache_path, const Decoder::CodecStream &stream, const AudioParams ¶ms);
|
||||
|
||||
static bool AllConformsExist(const QVector<QString> &filenames);
|
||||
|
||||
private slots:
|
||||
void ConformTaskFinished(Task *task, bool succeeded);
|
||||
|
||||
+20
-21
@@ -24,6 +24,7 @@
|
||||
#include <QDebug>
|
||||
|
||||
#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<QString> &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<QString> &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<QString> &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<char**>(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;
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -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<QString> &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<QString>& 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<QString> &conform_filenames, const TimeRange &range, Footage::LoopMode loop_mode, const AudioParams ¶ms);
|
||||
|
||||
CodecStream stream_;
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ extern "C" {
|
||||
#include <QThread>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
|
||||
#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<QString> &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<AVSampleFormat>(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<uint8_t**>(&data),
|
||||
reinterpret_cast<uint8_t**>(data.to_raw_ptrs()),
|
||||
nb_samples,
|
||||
const_cast<const uint8_t**>(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<const char**>(reinterpret_cast<char**>(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<char*>(frame->data[0])) {
|
||||
delete [] data;
|
||||
}
|
||||
|
||||
SignalProcessingProgress(frame->pts, duration);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<QString>& filenames, const AudioParams ¶ms, const QAtomicInt* cancelled) override;
|
||||
virtual void CloseInternal() override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "planarfiledevice.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
PlanarFileDevice::PlanarFileDevice(QObject *parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
PlanarFileDevice::~PlanarFileDevice()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
bool PlanarFileDevice::open(const QVector<QString> &filenames, QIODevice::OpenMode mode)
|
||||
{
|
||||
if (isOpen()) {
|
||||
// Already open
|
||||
return false;
|
||||
}
|
||||
|
||||
files_.resize(filenames.size());
|
||||
files_.fill(nullptr);
|
||||
|
||||
for (int i=0; i<files_.size(); i++) {
|
||||
files_[i] = new QFile(filenames.at(i));
|
||||
if (!files_[i]->open(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; i<files_.size(); i++) {
|
||||
// Kind of clunky but should be largely fine
|
||||
ret = files_[i]->read(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; i<files_.size(); i++) {
|
||||
// Kind of clunky but should be largely fine
|
||||
ret = files_[i]->write(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; i<files_.size(); i++) {
|
||||
ret = files_[i]->seek(pos) & ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void PlanarFileDevice::close()
|
||||
{
|
||||
for (int i=0; i<files_.size(); i++) {
|
||||
QFile *f = files_.at(i);
|
||||
if (f) {
|
||||
if (f->isOpen()) {
|
||||
f->close();
|
||||
}
|
||||
delete f;
|
||||
}
|
||||
}
|
||||
files_.clear();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef PLANARFILEDEVICE_H
|
||||
#define PLANARFILEDEVICE_H
|
||||
|
||||
#include <QFile>
|
||||
#include <QObject>
|
||||
|
||||
#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<QString> &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<QFile*> files_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // PLANARFILEDEVICE_H
|
||||
+18
-25
@@ -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<const float*>(bytes.constData());
|
||||
|
||||
for (int i=0;i<total_samples;i++) {
|
||||
int channel = i % audio_params.channel_count();
|
||||
int index = i / audio_params.channel_count();
|
||||
|
||||
buffer->data_[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<audio_params_.channel_count(); i++) {
|
||||
data_[i].resize(sample_count_per_channel_);
|
||||
}
|
||||
|
||||
update_raw();
|
||||
}
|
||||
|
||||
void SampleBuffer::destroy()
|
||||
{
|
||||
data_.clear();
|
||||
raw_ptrs_.clear();
|
||||
}
|
||||
|
||||
void SampleBuffer::reverse()
|
||||
@@ -178,6 +157,7 @@ void SampleBuffer::speed(double speed)
|
||||
}
|
||||
|
||||
data_ = output_data;
|
||||
update_raw();
|
||||
}
|
||||
|
||||
void SampleBuffer::transform_volume(float f)
|
||||
@@ -214,6 +194,11 @@ void SampleBuffer::silence()
|
||||
}
|
||||
|
||||
void SampleBuffer::silence(int start_sample, int end_sample)
|
||||
{
|
||||
silence_bytes(start_sample * sizeof(float), end_sample * sizeof(float));
|
||||
}
|
||||
|
||||
void SampleBuffer::silence_bytes(int start_byte, int end_byte)
|
||||
{
|
||||
if (!is_allocated()) {
|
||||
qWarning() << "Tried to fill an unallocated sample buffer";
|
||||
@@ -221,7 +206,7 @@ void SampleBuffer::silence(int start_sample, int end_sample)
|
||||
}
|
||||
|
||||
for (int i=0;i<audio_params().channel_count();i++) {
|
||||
memset(data_[i].data(), start_sample * sizeof(float), (end_sample-start_sample) * sizeof(float));
|
||||
memset(reinterpret_cast<char*>(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<raw_ptrs_.size(); i++) {
|
||||
raw_ptrs_[i] = data_[i].data();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -46,7 +46,6 @@ public:
|
||||
static SampleBufferPtr Create();
|
||||
static SampleBufferPtr CreateAllocated(const AudioParams& audio_params, const rational& length);
|
||||
static SampleBufferPtr CreateAllocated(const AudioParams& audio_params, int samples_per_channel);
|
||||
static SampleBufferPtr CreateFromPackedData(const AudioParams& audio_params, const QByteArray& bytes);
|
||||
|
||||
DISABLE_COPY_MOVE(SampleBuffer)
|
||||
|
||||
@@ -66,6 +65,11 @@ public:
|
||||
return data_.at(channel).constData();
|
||||
}
|
||||
|
||||
float **to_raw_ptrs()
|
||||
{
|
||||
return raw_ptrs_.data();
|
||||
}
|
||||
|
||||
bool is_allocated() const;
|
||||
void allocate();
|
||||
void destroy();
|
||||
@@ -79,6 +83,7 @@ public:
|
||||
|
||||
void silence();
|
||||
void silence(int start_sample, int end_sample);
|
||||
void silence_bytes(int start_byte, int end_byte);
|
||||
|
||||
void set(int channel, const float* data, int sample_offset, int sample_length);
|
||||
void set(int channel, const float* data, int sample_length)
|
||||
@@ -87,11 +92,14 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
void update_raw();
|
||||
|
||||
AudioParams audio_params_;
|
||||
|
||||
int sample_count_per_channel_;
|
||||
|
||||
QVector< QVector<float> > data_;
|
||||
QVector<float*> raw_ptrs_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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<QFutureWatcher< QVector<HashData> >*> hash_tasks_;
|
||||
|
||||
@@ -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<QString> &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();
|
||||
|
||||
|
||||
@@ -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<QString> &output_filenames);
|
||||
|
||||
protected:
|
||||
virtual bool Run() override;
|
||||
@@ -44,7 +44,7 @@ private:
|
||||
|
||||
AudioParams params_;
|
||||
|
||||
QString output_filename_;
|
||||
QVector<QString> output_filenames_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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_) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user