diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2b1f3471a..337ca5784 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -41,7 +41,7 @@ endif()
find_package(OpenGL REQUIRED)
-find_package(OpenColorIO REQUIRED)
+find_package(OpenColorIO 2.0.0 REQUIRED)
find_package(OpenImageIO 1.6 REQUIRED)
diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt
index a5177e705..7e7487ba5 100644
--- a/app/CMakeLists.txt
+++ b/app/CMakeLists.txt
@@ -41,6 +41,7 @@ add_subdirectory(project)
add_subdirectory(render)
add_subdirectory(shaders)
add_subdirectory(task)
+add_subdirectory(threading)
add_subdirectory(timeline)
add_subdirectory(tool)
add_subdirectory(ui)
diff --git a/app/audio/CMakeLists.txt b/app/audio/CMakeLists.txt
index b8b8e49a2..5b0976dc0 100644
--- a/app/audio/CMakeLists.txt
+++ b/app/audio/CMakeLists.txt
@@ -24,8 +24,6 @@ set(OLIVE_SOURCES
audio/outputdeviceproxy.cpp
audio/outputmanager.h
audio/outputmanager.cpp
- audio/sampleformat.h
- audio/sampleformat.cpp
audio/tempoprocessor.h
audio/tempoprocessor.cpp
PARENT_SCOPE
diff --git a/app/audio/audiomanager.cpp b/app/audio/audiomanager.cpp
index a0afa7cce..48daa0c9b 100644
--- a/app/audio/audiomanager.cpp
+++ b/app/audio/audiomanager.cpp
@@ -124,36 +124,8 @@ void AudioManager::SetOutputDevice(const QAudioDeviceInfo &info)
format.setChannelCount(output_params_.channel_count());
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
-
- switch (output_params_.format()) {
- case SampleFormat::SAMPLE_FMT_U8:
- format.setSampleSize(8);
- format.setSampleType(QAudioFormat::UnSignedInt);
- break;
- case SampleFormat::SAMPLE_FMT_S16:
- format.setSampleSize(16);
- format.setSampleType(QAudioFormat::SignedInt);
- break;
- case SampleFormat::SAMPLE_FMT_S32:
- format.setSampleSize(32);
- format.setSampleType(QAudioFormat::SignedInt);
- break;
- case SampleFormat::SAMPLE_FMT_S64:
- format.setSampleSize(64);
- format.setSampleType(QAudioFormat::SignedInt);
- break;
- case SampleFormat::SAMPLE_FMT_FLT:
- format.setSampleSize(32);
- format.setSampleType(QAudioFormat::Float);
- break;
- case SampleFormat::SAMPLE_FMT_DBL:
- format.setSampleSize(64);
- format.setSampleType(QAudioFormat::Float);
- break;
- case SampleFormat::SAMPLE_FMT_COUNT:
- case SampleFormat::SAMPLE_FMT_INVALID:
- abort();
- }
+ format.setSampleSize(output_params_.bits_per_sample());
+ format.setSampleType(AudioParams::GetQtSampleType(output_params_.format()));
if (info.isFormatSupported(format)) {
QMetaObject::invokeMethod(output_manager_,
diff --git a/app/audio/sampleformat.cpp b/app/audio/sampleformat.cpp
deleted file mode 100644
index f7af19c65..000000000
--- a/app/audio/sampleformat.cpp
+++ /dev/null
@@ -1,52 +0,0 @@
-/***
-
- Olive - Non-Linear Video Editor
- Copyright (C) 2019 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 "sampleformat.h"
-
-#include "core.h"
-
-OLIVE_NAMESPACE_ENTER
-
-const SampleFormat::Format SampleFormat::kInternalFormat = SAMPLE_FMT_FLT;
-
-QString SampleFormat::GetSampleFormatName(const SampleFormat::Format &f)
-{
- switch (f) {
- case SAMPLE_FMT_U8:
- return tr("Unsigned 8-bit");
- case SAMPLE_FMT_S16:
- return tr("Signed 16-bit");
- case SAMPLE_FMT_S32:
- return tr("Signed 32-bit");
- case SAMPLE_FMT_S64:
- return tr("Signed 64-bit");
- case SAMPLE_FMT_FLT:
- return tr("32-bit Float");
- case SAMPLE_FMT_DBL:
- return tr("64-bit Float");
- case SAMPLE_FMT_COUNT:
- case SAMPLE_FMT_INVALID:
- break;
- }
-
- return tr("Invalid");
-}
-
-OLIVE_NAMESPACE_EXIT
diff --git a/app/audio/tempoprocessor.cpp b/app/audio/tempoprocessor.cpp
index 2deda4f33..1cb56b468 100644
--- a/app/audio/tempoprocessor.cpp
+++ b/app/audio/tempoprocessor.cpp
@@ -28,7 +28,7 @@ extern "C" {
#include
-#include "codec/ffmpeg/ffmpegcommon.h"
+#include "common/ffmpegutils.h"
OLIVE_NAMESPACE_ENTER
@@ -75,7 +75,7 @@ bool TempoProcessor::Open(const AudioParams ¶ms, const double& speed)
1,
params_.sample_rate(),
params_.sample_rate(),
- FFmpegCommon::GetFFmpegSampleFormat(params_.format()),
+ FFmpegUtils::GetFFmpegSampleFormat(params_.format()),
params.channel_layout());
// Create buffer and buffersink
@@ -171,7 +171,7 @@ void TempoProcessor::Push(const char *data, int length)
// Allocate a buffer for the number of samples we got
src_frame->sample_rate = params_.sample_rate();
- src_frame->format = FFmpegCommon::GetFFmpegSampleFormat(params_.format());
+ src_frame->format = FFmpegUtils::GetFFmpegSampleFormat(params_.format());
src_frame->channel_layout = params_.channel_layout();
src_frame->nb_samples = params_.bytes_to_samples(length);
src_frame->pts = timestamp_;
diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp
index d4dc7108b..9841fbf4f 100644
--- a/app/codec/decoder.cpp
+++ b/app/codec/decoder.cpp
@@ -24,11 +24,11 @@
#include
#include
-#include "codec/ffmpeg/ffmpegcommon.h"
#include "codec/ffmpeg/ffmpegdecoder.h"
#include "codec/oiio/oiiodecoder.h"
#include "codec/waveinput.h"
#include "codec/waveoutput.h"
+#include "common/ffmpegutils.h"
#include "common/filefunctions.h"
#include "common/timecodefunctions.h"
#ifdef USE_OTIO
@@ -39,55 +39,160 @@
OLIVE_NAMESPACE_ENTER
+QMutex Decoder::currently_conforming_mutex_;
+QWaitCondition Decoder::currently_conforming_wait_cond_;
+QVector Decoder::currently_conforming_;
+
Decoder::Decoder() :
- open_(false),
stream_(nullptr)
{
}
-Decoder::Decoder(Stream *fs) :
- open_(false),
- stream_(fs)
+bool Decoder::Open(StreamPtr fs)
{
+ QMutexLocker locker(&mutex_);
+
+ if (stream_) {
+ // Decoder is already open. Return TRUE if the stream is the stream we have, or FALSE if not.
+ if (stream_ == fs) {
+ return true;
+ } else {
+ qWarning() << "Tried to open a decoder that was already open with another stream";
+ return false;
+ }
+ } else {
+ // Stream was not open, try opening it now
+ if (fs == nullptr) {
+ // Cannot open null stream
+ qCritical() << "Decoder attempted to open null stream";
+ return false;
+ }
+
+ if (fs->footage()->decoder() != id()) {
+ qCritical() << "Tried to open footage in incorrect decoder";
+ return false;
+ }
+
+ // Set stream
+ stream_ = fs;
+
+ // Try open internal
+ if (OpenInternal()) {
+ return true;
+ } else {
+ // Unset stream
+ CloseInternal();
+ stream_ = nullptr;
+ return false;
+ }
+ }
}
-StreamPtr Decoder::stream() const
+FramePtr Decoder::RetrieveVideo(const rational &timecode, const int ÷r)
{
- return stream_;
+ QMutexLocker locker(&mutex_);
+
+ if (!stream_) {
+ qCritical() << "Can't retrieve video on a closed decoder";
+ return nullptr;
+ }
+
+ if (!SupportsVideo()) {
+ qCritical() << "Decoder doesn't support video";
+ return nullptr;
+ }
+
+ if (stream_->type() != Stream::kVideo) {
+ qCritical() << "Tried to retrieve video from a non-video stream";
+ return nullptr;
+ }
+
+ return RetrieveVideoInternal(timecode, divider);
}
-void Decoder::set_stream(StreamPtr fs)
+SampleBufferPtr Decoder::RetrieveAudio(const TimeRange &range, const AudioParams ¶ms, const QAtomicInt *cancelled)
{
- Close();
+ QMutexLocker locker(&mutex_);
- stream_ = fs;
+ if (!stream_) {
+ qCritical() << "Can't retrieve audio on a closed decoder";
+ return nullptr;
+ }
+
+ if (!SupportsAudio()) {
+ qCritical() << "Decoder doesn't support audio";
+ return nullptr;
+ }
+
+ if (stream_->type() != Stream::kAudio) {
+ qCritical() << "Tried to retrieve audio from a non-audio stream";
+ return nullptr;
+ }
+
+ // Determine if we already have a conformed version
+ QString conform_filename = GetConformedFilename(params);
+ CurrentlyConforming want_conform = {stream_, params};
+
+ currently_conforming_mutex_.lock();
+
+ // Wait for conform to complete
+ while (currently_conforming_.contains(want_conform)) {
+ currently_conforming_wait_cond_.wait(¤tly_conforming_mutex_);
+ }
+
+ // See if we got the conform
+ SampleBufferPtr buffer = RetrieveAudioFromConform(conform_filename, range);
+
+ if (!buffer) {
+ // We'll need to conform this ourselves
+ currently_conforming_.append(want_conform);
+ currently_conforming_mutex_.unlock();
+
+ // 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 = conform_filename;
+ working_fn.append(QStringLiteral(".working"));
+
+ if (ConformAudioInternal(working_fn, params, cancelled)) {
+ // Move file to standard conform name, making it clear this conform is ready for use
+ QFile::remove(conform_filename);
+ QFile::rename(working_fn, conform_filename);
+
+ // Return audio as planned
+ buffer = RetrieveAudioFromConform(conform_filename, range);
+ } else {
+ // Failed
+ qCritical() << "Failed to conform audio";
+ }
+
+ currently_conforming_mutex_.lock();
+ currently_conforming_.removeOne(want_conform);
+ currently_conforming_wait_cond_.wakeAll();
+ }
+
+ currently_conforming_mutex_.unlock();
+
+ return buffer;
}
-FramePtr Decoder::RetrieveVideo(const rational &/*timecode*/, const int &/*divider*/)
+void Decoder::Close()
{
- return nullptr;
-}
+ QMutexLocker locker(&mutex_);
-SampleBufferPtr Decoder::RetrieveAudio(const rational &/*timecode*/, const rational &/*length*/, const AudioParams &/*params*/)
-{
- return nullptr;
-}
-
-bool Decoder::SupportsVideo()
-{
- return false;
-}
-
-bool Decoder::SupportsAudio()
-{
- return false;
+ if (stream_) {
+ CloseInternal();
+ stream_ = nullptr;
+ } else {
+ qWarning() << "Tried to close a decoder that wasn't open";
+ }
}
/*
* DECODER STATIC PUBLIC MEMBERS
*/
-QVector ReceiveListOfAllDecoders() {
+QVector ReceiveListOfAllDecoders()
+{
QVector decoders;
// The order in which these decoders are added is their priority when probing. Hence FFmpeg should usually be last,
@@ -98,7 +203,7 @@ QVector ReceiveListOfAllDecoders() {
return decoders;
}
-FootagePtr Decoder::ProbeMedia(Project* project, const QString &filename, const QAtomicInt* cancelled)
+FootagePtr Decoder::Probe(Project* project, const QString &filename, const QAtomicInt* cancelled)
{
// Check for a valid filename
if (filename.isEmpty()) {
@@ -184,37 +289,6 @@ QString Decoder::GetIndexFilename()
return QDir(stream_->footage()->project()->cache_path()).filePath(FileFunctions::GetUniqueFileIdentifier(stream()->footage()->filename()).append(QString::number(stream()->index())));
}
-bool Decoder::ConformAudio(const QAtomicInt *, const AudioParams& )
-{
- return false;
-}
-
-bool Decoder::HasConformedVersion(const AudioParams ¶ms)
-{
- if (stream()->type() != Stream::kAudio) {
- return false;
- }
-
- AudioStreamPtr audio_stream = std::static_pointer_cast(stream());
-
- if (audio_stream->has_conformed_version(params)) {
- return true;
- }
-
- // Get indexed WAV file
- WaveInput input(GetIndexFilename());
-
- bool index_already_matches = false;
-
- if (input.open()) {
- index_already_matches = (input.params() == params);
-
- input.close();
- }
-
- return index_already_matches;
-}
-
void Decoder::SignalProcessingProgress(const int64_t &ts)
{
if (stream()->duration() != AV_NOPTS_VALUE && stream()->duration() != 0) {
@@ -267,4 +341,40 @@ int64_t Decoder::GetImageSequenceIndex(const QString &filename)
return number_only.toLongLong();
}
+FramePtr Decoder::RetrieveVideoInternal(const rational &timecode, const int ÷r)
+{
+ Q_UNUSED(timecode)
+ Q_UNUSED(divider)
+ return nullptr;
+}
+
+bool Decoder::ConformAudioInternal(const QString& filename, const AudioParams ¶ms, const QAtomicInt* cancelled)
+{
+ Q_UNUSED(filename)
+ Q_UNUSED(cancelled)
+ Q_UNUSED(params)
+ return false;
+}
+
+SampleBufferPtr Decoder::RetrieveAudioFromConform(const QString &conform_filename, const TimeRange& range)
+{
+ WaveInput input(conform_filename);
+
+ if (input.open()) {
+ const AudioParams& input_params = input.params();
+
+ // Read bytes from wav
+ QByteArray packed_data = input.read(input_params.time_to_bytes(range.in()),
+ input_params.time_to_bytes(range.length()));
+ input.close();
+
+ // Create sample buffer
+ SampleBufferPtr sample_buffer = SampleBuffer::CreateFromPackedData(input_params, packed_data);
+
+ return sample_buffer;
+ }
+
+ return nullptr;
+}
+
OLIVE_NAMESPACE_EXIT
diff --git a/app/codec/decoder.h b/app/codec/decoder.h
index a096d5b08..68ff8dbb5 100644
--- a/app/codec/decoder.h
+++ b/app/codec/decoder.h
@@ -27,6 +27,7 @@ extern "C" {
#include
#include
+#include
#include
#include "codec/frame.h"
@@ -68,117 +69,46 @@ public:
Decoder();
- Decoder(Stream* fs);
-
- DISABLE_COPY_MOVE(Decoder)
-
+ /**
+ * @brief Unique decoder ID
+ */
virtual QString id() = 0;
- StreamPtr stream() const;
- void set_stream(StreamPtr fs);
+ virtual bool SupportsVideo(){return false;}
+ virtual bool SupportsAudio(){return false;}
/**
- * @brief Probe a footage file and dump metadata about it
+ * @brief Open stream for decoding
*
- * When a Footage file is imported, we'll need to know whether Olive is equipped with a decoder for utilizing it
- * and metadata should be retrieved about it if so. For this purpose, the Footage object is passed through all
- * Probe() functions of available deocders until one returns TRUE. A FALSE return means the Decoder was unable to
- * parse this file and the next should be tried.
+ * This function is thread safe.
*
- * Probe() differs from Open() since it focuses on a file as a whole rather than one particular stream. Probe()
- * should be able to be run directly without calling Open() or Close() and should free its memory before returning.
- *
- * Probe() will never be called on an object that is also used for decoding. In other words, it will never be called
- * alongside Open() or Close() externally, so Probe() can use variables that would otherwise be used for decoding
- * without conflict.
- *
- * @param f
- *
- * A Footage object to probe. The Footage object will have a valid filename and will be empty prior to being sent
- * to this function (i.e. Footage::Clear() will not have to be called).
- *
- * @return
- *
- * TRUE if the Decoder was able to decode this file. FALSE if not. This function should have filled the Footage
- * object with metadata if it returns TRUE. Otherwise, the Footage object should be untouched.
+ * Returns TRUE if stream could be opened successfully. Also returns TRUE if the decoder is
+ * already open and the stream == the stream provided. Returns FALSE if the stream couldn't
+ * be opened OR if already open and the stream is NOT the same.
*/
- virtual FootagePtr Probe(const QString& filename, const QAtomicInt* cancelled) const = 0;
+ bool Open(StreamPtr fs);
/**
- * @brief Open media/allocate memory
+ * @brief Retrieves a video frame from footage
*
- * Any file handles or memory allocation that needs to be done before this instance of a Decoder can return data
- * should be done here.
+ * This function will always return a valid frame unless a fatal error occurs (in such case,
+ * nullptr will return). If the timecode is before the start of the footage, this function should
+ * return the first frame. Likewise, if it is after the timecode, this function should return the
+ * last frame.
*
- * @return
- *
- * TRUE if successful and ready to return data, FALSE if failed to open and unable to retrieve data. If the function
- * fails, any memory allocated should be free'd before returning FALSE, possibly by calling Close().
+ * This function is thread safe and can only run while the decoder is open. \see Open()
*/
- virtual bool Open() = 0;
+ FramePtr RetrieveVideo(const rational& timecode, const int& divider);
/**
- * @brief Retrieve video frame
+ * @brief Retrieve audio data from footage
*
- * The main function for retrieving video data from the Decoder. This function should always provide complete frame
- * data (i.e. no partial frames) at the timecode provided. The Decoder should perform any steps required to retrieve
- * a complete frame separate from the rest of the program, using any form of caching/indexing to keep this as
- * performant as possible.
+ * This function will always return a sample buffer unless a fatal error occurs (in such case,
+ * nullptr will return). The SampleBuffer should always have enough audio for the range provided.
*
- * It's acceptable for this function to check whether the Decoder is open, and call Open() if not. If Open() returns
- * false, this function should return nullptr.
- *
- * @param timecode
- *
- * The timecode (a rational in seconds) to retrieve the frame at. If there is not a frame at this precise location
- * this should be corrected internally to the closest fit for the timecode.
- *
- * @return
- *
- * A FramePtr of valid data at this timecode or nullptr if there was nothing to retrieve at the provided timecode or
- * the media could not be opened.
+ * This function is thread safe and can only run while the decoder is open. \see Open()
*/
- virtual FramePtr RetrieveVideo(const rational& timecode, const int& divider);
-
- /**
- * @brief Retrieve video frame
- *
- * The main function for retrieving audio data from the Decoder. This function should always provide complete frame
- * data (i.e. no missing samples) at the timecode and length requested. The Decoder should perform any steps
- * required to retrieve a complete frame separate from the rest of the program, using any form of caching/indexing
- * to keep this as performant as possible.
- *
- * It's acceptable for this function to check whether the Decoder is open, and call Open() if not. If Open() returns
- * false, this function should return nullptr.
- *
- * @param timecode
- *
- * The starting timecode (a rational in seconds) to retrieve the data at.
- *
- * @param length
- *
- * The total length of audio data to retrieve (a rational in seconds).
- *
- * @return
- *
- * A FramePtr of valid data at this timecode of the requested length or nullptr if there was nothing to retrieve at
- * the provided timecode or the media could not be opened.
- */
- virtual SampleBufferPtr RetrieveAudio(const rational& timecode, const rational& length, const AudioParams& params);
-
- virtual bool SupportsVideo();
- virtual bool SupportsAudio();
-
- /**
- * @brief Close media/deallocate memory
- *
- * Any file handles or memory allocations opened in Open() should be cleaned up here.
- *
- * As the main memory freeing function, it's good practice to call this in Open() if there's an error that prevents
- * correct function before Open() returns. As such, Close() should be prepared for not all memory/file handles to
- * have been opened successfully.
- */
- virtual void Close() = 0;
+ SampleBufferPtr RetrieveAudio(const TimeRange& range, const AudioParams& params, const QAtomicInt *cancelled);
/**
* @brief Try to probe a Footage file by passing it through all available Decoders
@@ -199,7 +129,27 @@ public:
*
* TRUE if a Decoder was successfully able to parse and probe this file. FALSE if not.
*/
- static FootagePtr ProbeMedia(Project *project, const QString& filename, const QAtomicInt *cancelled);
+ static FootagePtr Probe(Project *project, const QString& filename, const QAtomicInt *cancelled);
+
+ /**
+ * @brief Generate a Footage object from a file
+ *
+ * If this decoder is able to parse this file, it will return a valid FootagePtr. Otherwise, it
+ * will return nullptr.
+ *
+ * For sub-classes, this function should be effectively static. We can't do virtual static
+ * functions in C++, but it should hold and access no state during its run.
+ *
+ * This function is re-entrant.
+ */
+ virtual FootagePtr Probe(const QString& filename, const QAtomicInt* cancelled) const = 0;
+
+ /**
+ * @brief Closes media/deallocates memory
+ *
+ * This function is thread safe and can only run while the decoder is open. \see Open()
+ */
+ void Close();
/**
* @brief Create a Decoder instance using a Decoder ID
@@ -210,42 +160,45 @@ public:
*/
static DecoderPtr CreateFromID(const QString& id);
- /**
- * @brief AUDIO ONLY: Produces a complete PCM extraction of the audio stream
- *
- * Internally, our render engine only deals with PCM since it provides the least headaches and
- * modern computers have the processing power to do it.
- *
- * Resamples and converts the currently open audio to match the params. If the audio doesn't need
- * conforming (e.g. audio params already match or a conformed match already exists), this function
- * will return immediately. Otherwise it will block the calling thread until the conform is
- * complete. This function should therefore only be called from a background render thread.
- *
- * All audio decoders must override this. It's not pure since video decoders don't need to use
- * this, but default behavior will abort since it should never be called.
- */
- virtual bool ConformAudio(const QAtomicInt* cancelled, const AudioParams ¶ms);
-
- /**
- * @brief AUDIO ONLY: Returns whether a transcode of this audio matching the specified params
- * already exists
- */
- bool HasConformedVersion(const AudioParams& params);
-
static QString TransformImageSequenceFileName(const QString& filename, const int64_t& number);
static int GetImageSequenceDigitCount(const QString& filename);
static int64_t GetImageSequenceIndex(const QString& filename);
-signals:
- /**
- * @brief While indexing, this signal will provide progress as a percentage (0-100 inclusive) if
- * available
- */
- void IndexProgress(double);
-
protected:
+ /**
+ * @brief Internal open function
+ *
+ * Sub-classes must override this function. Function will already be mutexed, so there is no need
+ * to worry about thread safety. Also many other sanity checks will be done before this, so
+ * sub-classes only need to worry about their own opening functions. It is guaranteed that the
+ * decoder is not open yet and that the footage stream was from that sub-classes probe function.
+ *
+ * Return TRUE if everything opened successfully and the decoder is ready to work. Otherwise,
+ * return FALSE. If this function returns false, Decoder will call CloseInternal to clean any
+ * memory allocated during OpenInternal.
+ */
+ virtual bool OpenInternal() = 0;
+
+ /**
+ * @brief Internal close function
+ *
+ * Sub-classes must override this function. Function should be able to safely clear all allocated
+ * memory. It may be called even if Open() didn't complete or RetrieveVideo() was never called.
+ */
+ virtual void CloseInternal() = 0;
+
+ /**
+ * @brief Internal frame retrieval function
+ *
+ * Sub-classes must override this function IF they support video. Function is already mutexed
+ * so sub-classes don't need to worry about thread safety.
+ */
+ virtual FramePtr RetrieveVideoInternal(const rational& timecode, const int& divider);
+
+ virtual bool ConformAudioInternal(const QString& filename, const AudioParams ¶ms, const QAtomicInt* cancelled);
+
void SignalProcessingProgress(const int64_t& ts);
/**
@@ -255,11 +208,44 @@ protected:
QString GetIndexFilename();
- bool open_;
+ struct CurrentlyConforming {
+ StreamPtr stream;
+ AudioParams params;
+
+ bool operator==(const CurrentlyConforming& rhs) const
+ {
+ return this->stream == rhs.stream && this->params == rhs.params;
+ }
+ };
+
+ /**
+ * @brief Return currently open stream
+ *
+ * This function is NOT thread safe and should therefore only be called by thread safe functions.
+ */
+ StreamPtr stream() const
+ {
+ return stream_;
+ }
+
+ static QMutex currently_conforming_mutex_;
+ static QWaitCondition currently_conforming_wait_cond_;
+ static QVector currently_conforming_;
+
+signals:
+ /**
+ * @brief While indexing, this signal will provide progress as a percentage (0-100 inclusive) if
+ * available
+ */
+ void IndexProgress(double);
private:
+ SampleBufferPtr RetrieveAudioFromConform(const QString& conform_filename, const TimeRange &range);
+
StreamPtr stream_;
+ QMutex mutex_;
+
};
OLIVE_NAMESPACE_EXIT
diff --git a/app/codec/encoder.cpp b/app/codec/encoder.cpp
index 0f648884e..b328e3135 100644
--- a/app/codec/encoder.cpp
+++ b/app/codec/encoder.cpp
@@ -232,7 +232,7 @@ void EncodingParams::Save(QXmlStreamWriter *writer) const
Encoder* Encoder::CreateFromID(const QString &id, const EncodingParams& params)
{
Q_UNUSED(id)
-
+
return new FFmpegEncoder(params);
}
diff --git a/app/codec/encoder.h b/app/codec/encoder.h
index 4acf38bde..9cd9547a0 100644
--- a/app/codec/encoder.h
+++ b/app/codec/encoder.h
@@ -122,6 +122,11 @@ public:
virtual void Close() = 0;
+ virtual VideoParams::Format GetDesiredPixelFormat() const
+ {
+ return VideoParams::kFormatInvalid;
+ }
+
private:
EncodingParams params_;
diff --git a/app/codec/ffmpeg/CMakeLists.txt b/app/codec/ffmpeg/CMakeLists.txt
index 12267fe76..7c26e24bf 100644
--- a/app/codec/ffmpeg/CMakeLists.txt
+++ b/app/codec/ffmpeg/CMakeLists.txt
@@ -17,8 +17,6 @@
set(OLIVE_SOURCES
${OLIVE_SOURCES}
codec/ffmpeg/avframeptr.h
- codec/ffmpeg/ffmpegcommon.h
- codec/ffmpeg/ffmpegcommon.cpp
codec/ffmpeg/ffmpegdecoder.h
codec/ffmpeg/ffmpegdecoder.cpp
codec/ffmpeg/ffmpegencoder.h
diff --git a/app/codec/ffmpeg/ffmpegcommon.cpp b/app/codec/ffmpeg/ffmpegcommon.cpp
deleted file mode 100644
index f66804048..000000000
--- a/app/codec/ffmpeg/ffmpegcommon.cpp
+++ /dev/null
@@ -1,139 +0,0 @@
-/***
-
- Olive - Non-Linear Video Editor
- Copyright (C) 2019 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 "ffmpegcommon.h"
-
-OLIVE_NAMESPACE_ENTER
-
-AVPixelFormat FFmpegCommon::GetCompatiblePixelFormat(const AVPixelFormat &pix_fmt)
-{
- AVPixelFormat possible_pix_fmts[] = {
- AV_PIX_FMT_RGB24,
- AV_PIX_FMT_RGBA,
- AV_PIX_FMT_RGB48,
- AV_PIX_FMT_RGBA64,
- AV_PIX_FMT_NONE
- };
-
- return avcodec_find_best_pix_fmt_of_list(possible_pix_fmts,
- pix_fmt,
- 1,
- nullptr);
-}
-
-SampleFormat::Format FFmpegCommon::GetNativeSampleFormat(const AVSampleFormat &smp_fmt)
-{
- switch (smp_fmt) {
- case AV_SAMPLE_FMT_U8:
- return SampleFormat::SAMPLE_FMT_U8;
- case AV_SAMPLE_FMT_S16:
- return SampleFormat::SAMPLE_FMT_S16;
- case AV_SAMPLE_FMT_S32:
- return SampleFormat::SAMPLE_FMT_S32;
- case AV_SAMPLE_FMT_S64:
- return SampleFormat::SAMPLE_FMT_S64;
- case AV_SAMPLE_FMT_FLT:
- return SampleFormat::SAMPLE_FMT_FLT;
- case AV_SAMPLE_FMT_DBL:
- return SampleFormat::SAMPLE_FMT_DBL;
- case AV_SAMPLE_FMT_U8P :
- case AV_SAMPLE_FMT_S16P:
- case AV_SAMPLE_FMT_S32P:
- case AV_SAMPLE_FMT_S64P:
- case AV_SAMPLE_FMT_FLTP:
- case AV_SAMPLE_FMT_DBLP:
- case AV_SAMPLE_FMT_NONE:
- case AV_SAMPLE_FMT_NB:
- break;
- }
-
- return SampleFormat::SAMPLE_FMT_INVALID;
-}
-
-AVSampleFormat FFmpegCommon::GetFFmpegSampleFormat(const SampleFormat::Format &smp_fmt)
-{
- switch (smp_fmt) {
- case SampleFormat::SAMPLE_FMT_U8:
- return AV_SAMPLE_FMT_U8;
- case SampleFormat::SAMPLE_FMT_S16:
- return AV_SAMPLE_FMT_S16;
- case SampleFormat::SAMPLE_FMT_S32:
- return AV_SAMPLE_FMT_S32;
- case SampleFormat::SAMPLE_FMT_S64:
- return AV_SAMPLE_FMT_S64;
- case SampleFormat::SAMPLE_FMT_FLT:
- return AV_SAMPLE_FMT_FLT;
- case SampleFormat::SAMPLE_FMT_DBL:
- return AV_SAMPLE_FMT_DBL;
- case SampleFormat::SAMPLE_FMT_INVALID:
- case SampleFormat::SAMPLE_FMT_COUNT:
- break;
- }
-
- return AV_SAMPLE_FMT_NONE;
-}
-
-AVPixelFormat FFmpegCommon::GetFFmpegPixelFormat(const PixelFormat::Format &pix_fmt)
-{
- switch (pix_fmt) {
- case PixelFormat::PIX_FMT_RGBA8:
- return AV_PIX_FMT_RGBA;
- case PixelFormat::PIX_FMT_RGBA16U:
- return AV_PIX_FMT_RGBA64;
- case PixelFormat::PIX_FMT_RGB8:
- return AV_PIX_FMT_RGB24;
- case PixelFormat::PIX_FMT_RGB16U:
- return AV_PIX_FMT_RGB48;
- case PixelFormat::PIX_FMT_RGBA16F:
- case PixelFormat::PIX_FMT_RGBA32F:
- case PixelFormat::PIX_FMT_RGB16F:
- case PixelFormat::PIX_FMT_RGB32F:
- case PixelFormat::PIX_FMT_INVALID:
- case PixelFormat::PIX_FMT_COUNT:
- break;
- }
-
- return AV_PIX_FMT_NONE;
-}
-
-PixelFormat::Format FFmpegCommon::GetCompatiblePixelFormat(const PixelFormat::Format &pix_fmt)
-{
- switch (pix_fmt) {
- case PixelFormat::PIX_FMT_RGB8:
- return PixelFormat::PIX_FMT_RGB8;
- case PixelFormat::PIX_FMT_RGBA8:
- return PixelFormat::PIX_FMT_RGBA8;
- case PixelFormat::PIX_FMT_RGB16U:
- case PixelFormat::PIX_FMT_RGB16F:
- case PixelFormat::PIX_FMT_RGB32F:
- return PixelFormat::PIX_FMT_RGB16U;
- case PixelFormat::PIX_FMT_RGBA16U:
- case PixelFormat::PIX_FMT_RGBA16F:
- case PixelFormat::PIX_FMT_RGBA32F:
- return PixelFormat::PIX_FMT_RGBA16U;
- case PixelFormat::PIX_FMT_INVALID:
- case PixelFormat::PIX_FMT_COUNT:
- break;
- }
-
- return PixelFormat::PIX_FMT_INVALID;
-}
-
-OLIVE_NAMESPACE_EXIT
diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp
index c5a0f3546..e6718ead5 100644
--- a/app/codec/ffmpeg/ffmpegdecoder.cpp
+++ b/app/codec/ffmpeg/ffmpegdecoder.cpp
@@ -38,95 +38,58 @@ extern "C" {
#include "codec/waveinput.h"
#include "common/define.h"
+#include "common/ffmpegutils.h"
#include "common/filefunctions.h"
#include "common/functiontimer.h"
#include "common/timecodefunctions.h"
-#include "ffmpegcommon.h"
#include "render/framehashcache.h"
#include "render/diskmanager.h"
-#include "render/pixelformat.h"
OLIVE_NAMESPACE_ENTER
-QHash< Stream*, QList > FFmpegDecoder::instance_map_;
-QMutex FFmpegDecoder::instance_map_lock_;
-QHash< FFmpegDecoder::FFmpegFramePoolKey, FFmpegDecoder::FFmpegFramePoolValue > FFmpegDecoder::frame_pool_map_;
-
-// FIXME: Hardcoded value. It seems to work fine, but is there a possibility we should make
-// this a dynamic value somehow or a configurable value?
-const int FFmpegDecoderInstance::kMaxFrameLife = 2000;
-
FFmpegDecoder::FFmpegDecoder() :
scale_ctx_(nullptr),
- scale_divider_(0)
+ scale_divider_(0),
+ pool_(QThread::idealThreadCount()*2),
+ is_working_(false),
+ cache_at_zero_(false),
+ cache_at_eof_(false)
{
}
FFmpegDecoder::~FFmpegDecoder()
{
- Close();
+ CloseInternal();
}
-bool FFmpegDecoder::Open()
+bool FFmpegDecoder::OpenInternal()
{
- if (open_) {
+ if (instance_.Open(stream()->footage()->filename().toUtf8(), stream()->index())) {
+ AVStream* s = instance_.avstream();
+
+ // Store one second in the source's timebase
+ second_ts_ = qRound64(av_q2d(av_inv_q(s->time_base)));
+
+ if (stream()->type() == Stream::kVideo) {
+ // Get an Olive compatible AVPixelFormat
+ ideal_pix_fmt_ = FFmpegUtils::GetCompatiblePixelFormat(static_cast(s->codecpar->format));
+
+ // Determine which Olive native pixel format we retrieved
+ // Note that FFmpeg doesn't support float formats
+ native_pix_fmt_ = GetNativePixelFormat(ideal_pix_fmt_);
+ native_channel_count_ = GetNativeChannelCount(ideal_pix_fmt_);
+
+ if (native_pix_fmt_ == VideoParams::kFormatInvalid
+ || native_channel_count_ == 0) {
+ qDebug() << "Failed to find valid native pixel format for" << ideal_pix_fmt_;
+ return false;
+ }
+ }
+
return true;
}
- Q_ASSERT(stream());
-
- // Convert QString to a C string
- QByteArray fn_bytes = stream()->footage()->filename().toUtf8();
-
- FFmpegDecoderInstance* our_instance = new FFmpegDecoderInstance(fn_bytes.constData(), stream()->index());
-
- if (!our_instance->IsValid()) {
- delete our_instance;
- return false;
- }
-
- if (stream()->type() == Stream::kVideo) {
- // Get an Olive compatible AVPixelFormat
- src_pix_fmt_ = static_cast(our_instance->stream()->codecpar->format);
- ideal_pix_fmt_ = FFmpegCommon::GetCompatiblePixelFormat(src_pix_fmt_);
-
- // Determine which Olive native pixel format we retrieved
- // Note that FFmpeg doesn't support float formats
- native_pix_fmt_ = GetNativePixelFormat(ideal_pix_fmt_);
-
- Q_ASSERT(native_pix_fmt_ != PixelFormat::PIX_FMT_INVALID);
- }
-
- if (StreamUsesMultipleInstances(stream())) {
- // Video optimizes with multiple instances that we can swap between
- QMutexLocker map_locker(&instance_map_lock_);
-
- VideoStreamPtr vs = std::static_pointer_cast(stream());
-
- FFmpegFramePoolKey key = {vs->width(), vs->height(), src_pix_fmt_};
- FFmpegFramePoolValue& frame_pool = frame_pool_map_[key];
-
- if (!frame_pool.pool) {
- // Frames are allocated as threads * threads, to scale from each thread sharing one set
- // to all of them working individually
- int thread_count = QThread::idealThreadCount();
- int max_memory_frame_count = thread_count * thread_count;
- frame_pool.pool = new FFmpegFramePool(max_memory_frame_count);
- }
- frame_pool.handles++;
-
- our_instance->SetFramePool(frame_pool.pool);
-
- instance_map_[stream().get()].append(our_instance);
- } else {
- // Images, image sequences, and audio don't need an instance
- delete our_instance;
- }
-
- // All allocation succeeded so we set the state to open
- open_ = true;
-
- return true;
+ return false;
}
FramePtr FFmpegDecoder::RetrieveStillImage(const rational &timecode, const int ÷r)
@@ -136,51 +99,64 @@ FramePtr FFmpegDecoder::RetrieveStillImage(const rational &timecode, const int &
QString img_filename = stream()->footage()->filename();
+ int64_t ts;
+
// If it's an image sequence, we'll probably need to transform the filename
if (is->video_type() == VideoStream::kVideoTypeImageSequence) {
- int64_t ts = std::static_pointer_cast(stream())->get_time_in_timebase_units(timecode);
+ ts = std::static_pointer_cast(stream())->get_time_in_timebase_units(timecode);
img_filename = TransformImageSequenceFileName(stream()->footage()->filename(), ts);
+ } else {
+ ts = 0;
}
- FFmpegDecoderInstance i(img_filename.toUtf8(), stream()->index());
-
AVPacket* pkt = av_packet_alloc();
AVFrame* frame = av_frame_alloc();
FramePtr output_frame = nullptr;
+ Instance i;
+ i.Open(img_filename.toUtf8(), stream()->index());
+
int ret = i.GetFrame(pkt, frame);
if (ret >= 0) {
- output_frame = BuffersToNativeFrame(divider,
- is->width(),
- is->height(),
- 0,
- frame->data,
- frame->linesize);
+ // Create frame to return
+ output_frame = Frame::Create();
+ output_frame->set_video_params(VideoParams(frame->width,
+ frame->height,
+ native_pix_fmt_,
+ native_channel_count_,
+ std::static_pointer_cast(stream())->pixel_aspect_ratio(),
+ std::static_pointer_cast(stream())->interlacing(),
+ divider));
+ output_frame->set_timestamp(timecode);
+ output_frame->allocate();
+
+ uint8_t* copy_data = reinterpret_cast(output_frame->data());
+ int copy_linesize = output_frame->linesize_bytes();
+
+ FFmpegBufferToNativeBuffer(frame->data, frame->linesize, ©_data, ©_linesize);
} else {
qWarning() << "Failed to retrieve still image from decoder";
}
+ i.Close();
+
av_frame_free(&frame);
av_packet_free(&pkt);
return output_frame;
}
-FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode, const int ÷r)
+FramePtr FFmpegDecoder::RetrieveVideoInternal(const rational &timecode, const int ÷r)
{
- if (!open_) {
- qWarning() << "Tried to retrieve video on a decoder that's still closed";
- return nullptr;
- }
-
- if (stream()->type() != Stream::kVideo) {
- return nullptr;
- }
-
VideoStreamPtr vs = std::static_pointer_cast(stream());
+ if (scale_divider_ != divider) {
+ FreeScaler();
+ InitScaler(divider);
+ }
+
if (vs->video_type() == VideoStream::kVideoTypeStill
|| vs->video_type() == VideoStream::kVideoTypeImageSequence) {
@@ -188,153 +164,39 @@ FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode, const int &divid
} else {
- FFmpegFramePool::ElementPtr return_frame = nullptr;
-
int64_t target_ts = vs->get_time_in_timebase_units(timecode);
- FFmpegDecoderInstance* working_instance = nullptr;
-
int divided_width = VideoParams::GetScaledDimension(vs->width(), divider);
int divided_height = VideoParams::GetScaledDimension(vs->height(), divider);
- // Find instance
- do {
- QMutexLocker list_locker(&instance_map_lock_);
+ if (pool_.width() != divided_width || pool_.height() != divided_height) {
+ // Clear all instance queues
+ ClearFrameCache();
- const QList& instances = instance_map_.value(stream().get());
-
- FFmpegFramePool* pool = frame_pool_map_.value({vs->width(), vs->height(), src_pix_fmt_}).pool;
-
- if (pool->width() != divided_width || pool->height() != divided_height) {
- // Clear all instance queues
- foreach (FFmpegDecoderInstance* i, instances) {
- i->ClearFrameCache();
- }
-
- // Set new frame pool parameters
- pool->SetParameters(divided_width, divided_height, src_pix_fmt_);
- }
-
- QList non_ideal_contenders;
-
- foreach (FFmpegDecoderInstance* i, instances) {
-
- i->cache_lock()->lock();
-
- if (i->CacheContainsTime(target_ts)) {
-
- // Found our instance, allow others to enter the list
-
- list_locker.unlock();
-
- // Get the frame from this cache
- return_frame = i->GetFrameFromCache(target_ts);
-
- // Got our frame, allow cache to continue
- i->cache_lock()->unlock();
- break;
-
- } else if (i->CacheWillContainTime(target_ts) || i->CacheCouldContainTime(target_ts)) {
-
- // Found our instance, allow others to enter the list
- list_locker.unlock();
-
- // If the instance is currently in use, enter into a loop of seeing from frames come up next in case one is ours
- if (i->IsWorking()) {
-
- do {
- // Allow instance to continue to the next frame
- i->cache_wait_cond()->wait(i->cache_lock());
-
- // See if the cache now contains this frame, if so we'll exit this loop
- if (i->CacheContainsTime(target_ts)) {
-
- // Grab the frame
- return_frame = i->GetFrameFromCache(target_ts);
-
- // We can release this worker now since we don't need it anymore
- i->cache_lock()->unlock();
-
- } else if (!i->IsWorking()) {
-
- // This instance finished and we didn't get our frame, we'll take it and continue it
- working_instance = i;
- break;
-
- }
- } while (!return_frame);
-
- } else {
- // Otherwise, we'll grab this instance and continue it ourselves
- working_instance = i;
- }
-
- break;
-
- } else if (i->IsWorking()) {
-
- // Ignore currently working instances
- i->cache_lock()->unlock();
-
- } else if (i->CacheIsEmpty()) {
-
- // Prioritize this cache over others (leaves this instance LOCKED in case we end up using it later)
- non_ideal_contenders.prepend(i);
-
- } else {
-
- // De-prioritize this cache (leaves this instance LOCKED in case we end up using it later)
- non_ideal_contenders.append(i);
-
- }
- }
-
- // If we didn't find a suitable contender, grab the first non-suitable and roll with that
- if (!return_frame && !working_instance && !non_ideal_contenders.isEmpty()) {
- working_instance = non_ideal_contenders.takeFirst();
- }
-
- // For all instances we left locked but didn't end up using, lock them now
- foreach (FFmpegDecoderInstance* unsuitable_instance, non_ideal_contenders) {
- unsuitable_instance->cache_lock()->unlock();
- }
- } while (!return_frame && !working_instance);
-
- if (!return_frame && working_instance) {
-
- // This instance SHOULD remain locked from our earlier loop, making this operation safe
- working_instance->SetWorking(true);
-
- // Retrieve frame
- return_frame = working_instance->RetrieveFrame(target_ts, divider, true);
-
- // Set working to false and wake any threads waiting
- working_instance->cache_lock()->lock();
- working_instance->SetWorking(false);
- working_instance->cache_wait_cond()->wakeAll();
- working_instance->cache_lock()->unlock();
+ // Set new frame pool parameters
+ pool_.SetParameters(divided_width, divided_height, native_pix_fmt_, native_channel_count_);
}
+ // Retrieve frame
+ FFmpegFramePool::ElementPtr return_frame = RetrieveFrame(target_ts, divider);
+
// We found the frame, we'll return a copy
if (return_frame) {
- // Align buffer to data/linesize points that can be passed to sws_scale
- uint8_t* input_data[4];
- int input_linesize[4];
+ FramePtr copy = Frame::Create();
+ copy->set_video_params(VideoParams(vs->width(),
+ vs->height(),
+ native_pix_fmt_,
+ native_channel_count_,
+ std::static_pointer_cast(stream())->pixel_aspect_ratio(),
+ std::static_pointer_cast(stream())->interlacing(),
+ divider));
+ copy->set_timestamp(timecode);
+ copy->allocate();
- av_image_fill_arrays(input_data,
- input_linesize,
- reinterpret_cast(return_frame->data()),
- src_pix_fmt_,
- divided_width,
- divided_height,
- 1);
+ // This data will already match the frame
+ memcpy(copy->data(), return_frame->data(), copy->allocated_size());
- return BuffersToNativeFrame(divider,
- vs->width(),
- vs->height(),
- timecode,
- input_data,
- input_linesize);
+ return copy;
}
}
@@ -342,97 +204,13 @@ FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode, const int &divid
return nullptr;
}
-SampleBufferPtr FFmpegDecoder::RetrieveAudio(const rational &timecode, const rational &length, const AudioParams ¶ms)
+void FFmpegDecoder::CloseInternal()
{
- if (!open_) {
- qWarning() << "Tried to retrieve audio on a decoder that's still closed";
- return nullptr;
- }
+ ClearFrameCache();
- if (stream()->type() != Stream::kAudio) {
- return nullptr;
- }
+ instance_.Close();
- QString wav_fn = GetConformedFilename(params);
- WaveInput input(wav_fn);
-
- if (input.open()) {
- const AudioParams& input_params = input.params();
-
- // Read bytes from wav
- QByteArray packed_data = input.read(input_params.time_to_bytes(timecode), input_params.time_to_bytes(length));
- input.close();
-
- // Create sample buffer
- SampleBufferPtr sample_buffer = SampleBuffer::CreateFromPackedData(input_params, packed_data);
-
- return sample_buffer;
- }
-
- qCritical() << "Failed to open cached file" << wav_fn;
-
- return nullptr;
-}
-
-void FFmpegDecoder::Close()
-{
- if (stream() && StreamUsesMultipleInstances(stream())) {
- // Clear whichever instance is not in use and is least useful (there are only ever as many instances as there are
- // threads so if this thread is closing, an instance MUST be inactive)
- QMutexLocker l(&instance_map_lock_);
-
- QList list = instance_map_.value(stream().get());
-
- if (!list.isEmpty()) {
- // Rank the instances by least useful (the top one should be one that isn't working and isn't in use)
- QList least_useful;
-
- foreach (FFmpegDecoderInstance* i, list) {
- i->cache_lock()->lock();
-
- if (i->IsWorking()) {
- // Don't bother any currently working instances
- i->cache_lock()->unlock();
- continue;
- }
-
- if (i->CacheIsEmpty()) {
- least_useful.prepend(i);
- } else {
- least_useful.append(i);
- }
- }
-
- // Remove the least useful from the list and re-insert it into the map
- FFmpegDecoderInstance* least_useful_instance = least_useful.first();
- list.removeOne(least_useful_instance);
- instance_map_.insert(stream().get(), list);
-
- // If there are no more instances, destroy frame pool
- VideoStreamPtr vs = std::static_pointer_cast(stream());
- FFmpegFramePoolKey frame_pool_key = {vs->width(), vs->height(), src_pix_fmt_};
- FFmpegFramePoolValue& frame_pool_info = frame_pool_map_[frame_pool_key];
- frame_pool_info.handles--;
-
- if (frame_pool_info.handles == 0) {
- delete frame_pool_info.pool;
- frame_pool_map_.remove(frame_pool_key);
- }
-
- // We're done with the list now, we can unlock it and allow others to use it
- l.unlock();
-
- // Unlock all the instances we locked
- foreach (FFmpegDecoderInstance* i, least_useful) {
- i->cache_lock()->unlock();
- }
-
- // Delete this least useful instance now that we've definitely taken ownership of it
- least_useful_instance->deleteLater();
- }
- }
-
- ClearResources();
+ FreeScaler();
}
QString FFmpegDecoder::id()
@@ -440,16 +218,6 @@ QString FFmpegDecoder::id()
return QStringLiteral("ffmpeg");
}
-bool FFmpegDecoder::SupportsVideo()
-{
- return true;
-}
-
-bool FFmpegDecoder::SupportsAudio()
-{
- return true;
-}
-
FootagePtr FFmpegDecoder::Probe(const QString& filename, const QAtomicInt* cancelled) const
{
// Variable for receiving errors from FFmpeg
@@ -484,119 +252,134 @@ FootagePtr FFmpegDecoder::Probe(const QString& filename, const QAtomicInt* cance
StreamPtr str;
- if (avstream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && decoder) {
+ if (decoder
+ && (avstream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO
+ || avstream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)) {
- bool image_is_still = false;
- rational pixel_aspect_ratio;
- rational frame_rate;
- VideoParams::Interlacing interlacing = VideoParams::kInterlaceNone;
+ if (avstream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
- {
- // Read at least two frames to get more information about this video stream
- AVPacket* pkt = av_packet_alloc();
- AVFrame* frame = av_frame_alloc();
+ bool image_is_still = false;
+ rational pixel_aspect_ratio;
+ rational frame_rate;
+ VideoParams::Interlacing interlacing = VideoParams::kInterlaceNone;
{
- FFmpegDecoderInstance instance(filename_c, i);
+ // Read at least two frames to get more information about this video stream
+ AVPacket* pkt = av_packet_alloc();
+ AVFrame* frame = av_frame_alloc();
- // Read first frame and retrieve some metadata
- if (instance.GetFrame(pkt, frame) >= 0) {
- // Check if video is interlaced and what field dominance it has if so
- if (frame->interlaced_frame) {
- if (frame->top_field_first) {
- interlacing = VideoParams::kInterlacedTopFirst;
- } else {
- interlacing = VideoParams::kInterlacedBottomFirst;
+ {
+ Instance instance;
+ instance.Open(filename.toUtf8(), avstream->index);
+
+ // Read first frame and retrieve some metadata
+ if (instance.GetFrame(pkt, frame) >= 0) {
+ // Check if video is interlaced and what field dominance it has if so
+ if (frame->interlaced_frame) {
+ if (frame->top_field_first) {
+ interlacing = VideoParams::kInterlacedTopFirst;
+ } else {
+ interlacing = VideoParams::kInterlacedBottomFirst;
+ }
}
+
+ pixel_aspect_ratio = av_guess_sample_aspect_ratio(instance.fmt_ctx(),
+ instance.avstream(),
+ frame);
+
+ frame_rate = av_guess_frame_rate(instance.fmt_ctx(),
+ instance.avstream(),
+ frame);
}
- pixel_aspect_ratio = av_guess_sample_aspect_ratio(instance.fmt_ctx(),
- instance.stream(),
- frame);
+ // Read second frame
+ int ret = instance.GetFrame(pkt, frame);
- frame_rate = av_guess_frame_rate(instance.fmt_ctx(),
- instance.stream(),
- frame);
- }
+ if (ret >= 0) {
+ // Check if we need a manual duration
+ if (avstream->duration == AV_NOPTS_VALUE) {
+ int64_t new_dur;
- // Read second frame
- int ret = instance.GetFrame(pkt, frame);
+ do {
+ new_dur = frame->pts;
+ } while (instance.GetFrame(pkt, frame) >= 0);
- if (ret >= 0) {
- // Check if we need a manual duration
- if (avstream->duration == AV_NOPTS_VALUE) {
- int64_t new_dur;
-
- do {
- new_dur = frame->pts;
- } while (instance.GetFrame(pkt, frame) >= 0);
-
- avstream->duration = new_dur;
+ avstream->duration = new_dur;
+ }
+ } else if (ret == AVERROR_EOF) {
+ // Video has only one frame in it, treat it like a still image
+ image_is_still = true;
}
- } else if (ret == AVERROR_EOF) {
- // Video has only one frame in it, treat it like a still image
- image_is_still = true;
+
+ instance.Close();
}
+
+ av_frame_free(&frame);
+ av_packet_free(&pkt);
}
- av_frame_free(&frame);
- av_packet_free(&pkt);
- }
+ VideoStreamPtr video_stream = std::make_shared();
- VideoStreamPtr video_stream = std::make_shared();
+ if (image_is_still) {
+ video_stream->set_video_type(VideoStream::kVideoTypeStill);
+ } else {
+ video_stream->set_video_type(VideoStream::kVideoTypeVideo);
+
+ video_stream->set_frame_rate(frame_rate);
+ video_stream->set_start_time(avstream->start_time);
+ }
+
+ video_stream->set_width(avstream->codecpar->width);
+ video_stream->set_height(avstream->codecpar->height);
+ video_stream->set_interlacing(interlacing);
+ video_stream->set_pixel_aspect_ratio(pixel_aspect_ratio);
+
+ AVPixelFormat compatible_pix_fmt = FFmpegUtils::GetCompatiblePixelFormat(static_cast(avstream->codecpar->format));
+ video_stream->set_format(GetNativePixelFormat(compatible_pix_fmt));
+ video_stream->set_channel_count(GetNativeChannelCount(compatible_pix_fmt));
+
+ str = video_stream;
- if (image_is_still) {
- video_stream->set_video_type(VideoStream::kVideoTypeStill);
} else {
- video_stream->set_video_type(VideoStream::kVideoTypeVideo);
- video_stream->set_frame_rate(frame_rate);
- video_stream->set_start_time(avstream->start_time);
+ // Create an audio stream object
+ AudioStreamPtr audio_stream = std::make_shared();
+
+ uint64_t channel_layout = avstream->codecpar->channel_layout;
+ if (!channel_layout) {
+ channel_layout = static_cast(av_get_default_channel_layout(avstream->codecpar->channels));
+ }
+
+ audio_stream->set_channel_layout(channel_layout);
+ audio_stream->set_channels(avstream->codecpar->channels);
+ audio_stream->set_sample_rate(avstream->codecpar->sample_rate);
+
+ if (avstream->duration == AV_NOPTS_VALUE) {
+ // Loop through stream until we get the whole duration
+ Instance instance;
+ instance.Open(filename.toUtf8(), avstream->index);
+
+ AVPacket* pkt = av_packet_alloc();
+ AVFrame* frame = av_frame_alloc();
+
+ int64_t new_dur;
+
+ do {
+ new_dur = frame->pts;
+ } while (instance.GetFrame(pkt, frame) >= 0);
+
+ avstream->duration = new_dur;
+
+ av_frame_free(&frame);
+ av_packet_free(&pkt);
+
+ instance.Close();
+ }
+
+ str = audio_stream;
+
}
- video_stream->set_width(avstream->codecpar->width);
- video_stream->set_height(avstream->codecpar->height);
- video_stream->set_format(GetNativePixelFormat(FFmpegCommon::GetCompatiblePixelFormat(static_cast(avstream->codecpar->format))));
- video_stream->set_interlacing(interlacing);
- video_stream->set_pixel_aspect_ratio(pixel_aspect_ratio);
-
- str = video_stream;
-
- } else if (avstream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && decoder) {
-
- // Create an audio stream object
- AudioStreamPtr audio_stream = std::make_shared();
-
- uint64_t channel_layout = avstream->codecpar->channel_layout;
- if (!channel_layout) {
- channel_layout = static_cast(av_get_default_channel_layout(avstream->codecpar->channels));
- }
-
- audio_stream->set_channel_layout(channel_layout);
- audio_stream->set_channels(avstream->codecpar->channels);
- audio_stream->set_sample_rate(avstream->codecpar->sample_rate);
-
- if (avstream->duration == AV_NOPTS_VALUE) {
- // Loop through stream until we get the whole duration
- FFmpegDecoderInstance instance(filename_c, i);
-
- AVPacket* pkt = av_packet_alloc();
- AVFrame* frame = av_frame_alloc();
-
- int64_t new_dur;
-
- do {
- new_dur = frame->pts;
- } while (instance.GetFrame(pkt, frame) >= 0);
-
- avstream->duration = new_dur;
-
- av_frame_free(&frame);
- av_packet_free(&pkt);
- }
-
- str = audio_stream;
-
} else {
// This is data we can't utilize at the moment, but we make a Stream object anyway to keep parity with the file
@@ -658,50 +441,23 @@ FootagePtr FFmpegDecoder::Probe(const QString& filename, const QAtomicInt* cance
return footage;
}
-void FFmpegDecoder::FFmpegError(int error_code)
+QString FFmpegDecoder::FFmpegError(int error_code)
{
char err[1024];
av_strerror(error_code, err, 1024);
-
- Error(QStringLiteral("Error decoding %1 - %2 %3").arg(stream()->footage()->filename(),
- QString::number(error_code),
- err));
+ return QStringLiteral("%1 %2").arg(QString::number(error_code), err);
}
-void FFmpegDecoder::Error(const QString &s)
-{
- qWarning() << s;
-
- ClearResources();
-}
-
-bool FFmpegDecoder::ConformAudio(const QAtomicInt *cancelled, const AudioParams &p)
+bool FFmpegDecoder::ConformAudioInternal(const QString &filename, const AudioParams ¶ms, const QAtomicInt *cancelled)
{
// Iterate through each audio frame and extract the PCM data
AudioStreamPtr audio_stream = std::static_pointer_cast(stream());
- // Check if we already have a conform of this type
- QString conformed_fn = GetConformedFilename(p);
-
- if (QFileInfo::exists(conformed_fn)) {
-
- // If we have one, and we can open it correctly, we can use it as-is
- WaveInput input(conformed_fn);
- if (input.open()) {
- audio_stream->append_conformed_version(p);
-
- input.close();
-
- return true;
- }
- }
-
- // Conform doesn't exist, we'll have to produce one
- FFmpegDecoderInstance index_instance(stream()->footage()->filename().toUtf8(),
- stream()->index());
+ // Seek to starting point
+ instance_.Seek(0);
// Handle NULL channel layout
- uint64_t channel_layout = ValidateChannelLayout(index_instance.stream());
+ uint64_t channel_layout = ValidateChannelLayout(instance_.avstream());
if (!channel_layout) {
qCritical() << "Failed to determine channel layout of audio file, could not conform";
return false;
@@ -709,18 +465,18 @@ bool FFmpegDecoder::ConformAudio(const QAtomicInt *cancelled, const AudioParams
// Create resampling context
SwrContext* resampler = swr_alloc_set_opts(nullptr,
- p.channel_layout(),
- FFmpegCommon::GetFFmpegSampleFormat(p.format()),
- p.sample_rate(),
+ params.channel_layout(),
+ FFmpegUtils::GetFFmpegSampleFormat(params.format()),
+ params.sample_rate(),
channel_layout,
- static_cast(index_instance.stream()->codecpar->format),
- index_instance.stream()->codecpar->sample_rate,
+ static_cast(instance_.avstream()->codecpar->format),
+ instance_.avstream()->codecpar->sample_rate,
0,
nullptr);
swr_init(resampler);
- WaveOutput wave_out(conformed_fn, p);
+ WaveOutput wave_out(filename, params);
AVPacket* pkt = av_packet_alloc();
AVFrame* frame = av_frame_alloc();
@@ -735,7 +491,7 @@ bool FFmpegDecoder::ConformAudio(const QAtomicInt *cancelled, const AudioParams
break;
}
- ret = index_instance.GetFrame(pkt, frame);
+ ret = instance_.GetFrame(pkt, frame);
if (ret < 0) {
@@ -752,7 +508,7 @@ bool FFmpegDecoder::ConformAudio(const QAtomicInt *cancelled, const AudioParams
// Allocate buffers
int nb_samples = swr_get_out_samples(resampler, frame->nb_samples);
- char* data = new char[p.samples_to_bytes(nb_samples)];
+ char* data = new char[params.samples_to_bytes(nb_samples)];
// Resample audio to our destination parameters
nb_samples = swr_convert(resampler,
@@ -769,7 +525,7 @@ bool FFmpegDecoder::ConformAudio(const QAtomicInt *cancelled, const AudioParams
}
// Write packed WAV data to the disk cache
- wave_out.write(data, p.samples_to_bytes(nb_samples));
+ 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])) {
@@ -780,18 +536,6 @@ bool FFmpegDecoder::ConformAudio(const QAtomicInt *cancelled, const AudioParams
}
wave_out.close();
-
- if (success) {
-
- // If our conform succeeded, add it
- audio_stream->append_conformed_version(p);
-
- } else {
-
- // Audio index didn't complete, delete it
- QFile(conformed_fn).remove();
-
- }
} else {
qWarning() << "Failed to open WAVE output for indexing";
}
@@ -804,19 +548,31 @@ bool FFmpegDecoder::ConformAudio(const QAtomicInt *cancelled, const AudioParams
return success;
}
-PixelFormat::Format FFmpegDecoder::GetNativePixelFormat(AVPixelFormat pix_fmt)
+VideoParams::Format FFmpegDecoder::GetNativePixelFormat(AVPixelFormat pix_fmt)
{
switch (pix_fmt) {
case AV_PIX_FMT_RGB24:
- return PixelFormat::PIX_FMT_RGB8;
case AV_PIX_FMT_RGBA:
- return PixelFormat::PIX_FMT_RGBA8;
+ return VideoParams::kFormatUnsigned8;
case AV_PIX_FMT_RGB48:
- return PixelFormat::PIX_FMT_RGB16U;
case AV_PIX_FMT_RGBA64:
- return PixelFormat::PIX_FMT_RGBA16U;
+ return VideoParams::kFormatUnsigned16;
default:
- return PixelFormat::PIX_FMT_INVALID;
+ return VideoParams::kFormatInvalid;
+ }
+}
+
+int FFmpegDecoder::GetNativeChannelCount(AVPixelFormat pix_fmt)
+{
+ switch (pix_fmt) {
+ case AV_PIX_FMT_RGB24:
+ case AV_PIX_FMT_RGB48:
+ return VideoParams::kRGBChannelCount;
+ case AV_PIX_FMT_RGBA:
+ case AV_PIX_FMT_RGBA64:
+ return VideoParams::kRGBAChannelCount;
+ default:
+ return 0;
}
}
@@ -829,116 +585,15 @@ uint64_t FFmpegDecoder::ValidateChannelLayout(AVStream* stream)
return av_get_default_channel_layout(stream->codecpar->channels);
}
-bool FFmpegDecoder::StreamUsesMultipleInstances(StreamPtr stream)
+void FFmpegDecoder::FFmpegBufferToNativeBuffer(uint8_t **input_data, int *input_linesize, uint8_t** output_buffer, int* output_linesize)
{
- return stream->type() == Stream::kVideo
- && std::static_pointer_cast(stream)->video_type() != VideoStream::kVideoTypeStill;
-}
-
-FramePtr FFmpegDecoder::BuffersToNativeFrame(int divider, int width, int height, const rational& ts, uint8_t** input_data, int* input_linesize)
-{
- if (divider != scale_divider_) {
- FreeScaler();
- InitScaler(divider);
- }
-
- // Create frame to return
- FramePtr copy = Frame::Create();
- copy->set_video_params(VideoParams(width,
- height,
- native_pix_fmt_,
- std::static_pointer_cast(stream())->pixel_aspect_ratio(),
- std::static_pointer_cast(stream())->interlacing(),
- divider));
- copy->set_timestamp(ts);
- copy->allocate();
-
- // Convert frame to RGB/A for the rest of the pipeline
- uint8_t* output_data = reinterpret_cast(copy->data());
- int output_linesize = copy->linesize_bytes();
-
sws_scale(scale_ctx_,
input_data,
input_linesize,
0,
- VideoParams::GetScaledDimension(height, divider),
- &output_data,
- &output_linesize);
-
- return copy;
-}
-
-int FFmpegDecoderInstance::GetFrame(AVPacket *pkt, AVFrame *frame)
-{
- bool eof = false;
-
- int ret;
-
- // Clear any previous frames
- av_frame_unref(frame);
-
- while ((ret = avcodec_receive_frame(codec_ctx_, frame)) == AVERROR(EAGAIN) && !eof) {
-
- // Find next packet in the correct stream index
- do {
- // Free buffer in packet if there is one
- av_packet_unref(pkt);
-
- // Read packet from file
- ret = av_read_frame(fmt_ctx_, pkt);
- } while (pkt->stream_index != avstream_->index && ret >= 0);
-
- if (ret == AVERROR_EOF) {
- // Don't break so that receive gets called again, but don't try to read again
- eof = true;
-
- // Send a null packet to signal end of
- avcodec_send_packet(codec_ctx_, nullptr);
- } else if (ret < 0) {
- // Handle other error by breaking loop and returning the code we received
- break;
- } else {
- // Successful read, send the packet
- ret = avcodec_send_packet(codec_ctx_, pkt);
-
- // We don't need the packet anymore, so free it
- av_packet_unref(pkt);
-
- if (ret < 0) {
- break;
- }
- }
- }
-
- return ret;
-}
-
-QMutex *FFmpegDecoderInstance::cache_lock()
-{
- return &cache_lock_;
-}
-
-QWaitCondition *FFmpegDecoderInstance::cache_wait_cond()
-{
- return &cache_wait_cond_;
-}
-
-bool FFmpegDecoderInstance::IsWorking()
-{
- QMutexLocker locker(&is_working_mutex_);
- return is_working_;
-}
-
-void FFmpegDecoderInstance::SetWorking(bool working)
-{
- QMutexLocker locker(&is_working_mutex_);
- is_working_ = working;
-}
-
-void FFmpegDecoderInstance::Seek(int64_t timestamp)
-{
- avcodec_flush_buffers(codec_ctx_);
- av_seek_frame(fmt_ctx_, avstream_->index, timestamp, AVSEEK_FLAG_BACKWARD);
+ instance_.avstream()->codecpar->height,
+ output_buffer,
+ output_linesize);
}
/* OLD UNUSED CODE: Keeping this around in case the code proves useful
@@ -998,63 +653,51 @@ void FFmpegDecoder::CacheFrameToDisk(AVFrame *f)
}
*/
-void FFmpegDecoderInstance::ClearFrameCache()
+void FFmpegDecoder::ClearFrameCache()
{
cached_frames_.clear();
cache_at_eof_ = false;
cache_at_zero_ = false;
}
-FFmpegFramePool::ElementPtr FFmpegDecoderInstance::RetrieveFrame(const int64_t& target_ts, int divider, bool cache_is_locked)
+FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const int64_t& target_ts, int divider)
{
- if (!cache_is_locked) {
- cache_lock_.lock();
- }
-
- if (scale_divider_ != divider) {
- FreeScaler();
- InitScaler(divider);
- }
-
int64_t seek_ts = target_ts;
bool still_seeking = false;
- // CacheCouldContainTime uses cache_target_time_, so we'll temporarily set it to the last frame's TS
- if (!cached_frames_.isEmpty()) {
- cache_target_time_ = cached_frames_.last()->timestamp();
- }
-
// If the frame wasn't in the frame cache, see if this frame cache is too old to use
- if (!CacheCouldContainTime(target_ts)) {
+ if (cached_frames_.isEmpty()
+ || (target_ts < cached_frames_.first()->timestamp() || target_ts > cached_frames_.last()->timestamp() + 2*second_ts_)) {
ClearFrameCache();
- Seek(seek_ts);
+ instance_.Seek(seek_ts);
if (seek_ts == 0) {
cache_at_zero_ = true;
}
still_seeking = true;
+ } else {
+ // Search cache for frame
+ FFmpegFramePool::ElementPtr cached_frame = GetFrameFromCache(target_ts);
+ if (cached_frame) {
+ return cached_frame;
+ }
}
- cache_target_time_ = target_ts;
-
int ret;
AVPacket* pkt = av_packet_alloc();
FFmpegFramePool::ElementPtr return_frame = nullptr;
// Allocate a new frame
- AVFrameWrapper working_frame;
-
- bool unlocked = false;
+ AVFrame* working_frame = av_frame_alloc();
while (true) {
// Pull from the decoder
- ret = GetFrame(pkt, working_frame.frame());
+ ret = instance_.GetFrame(pkt, working_frame);
// Handle any errors that aren't EOF (EOF is handled later on)
if (ret < 0 && ret != AVERROR_EOF) {
- cache_lock_.unlock();
qCritical() << "Failed to retrieve frame:" << ret;
break;
}
@@ -1062,10 +705,10 @@ FFmpegFramePool::ElementPtr FFmpegDecoderInstance::RetrieveFrame(const int64_t&
if (still_seeking) {
// Handle a failure to seek (occurs on some media)
// We'll only be here if the frame cache was emptied earlier
- if (!cache_at_zero_ && (ret == AVERROR_EOF || working_frame.frame()->pts > target_ts)) {
+ if (!cache_at_zero_ && (ret == AVERROR_EOF || working_frame->pts > target_ts)) {
seek_ts = qMax(static_cast(0), seek_ts - second_ts_);
- Seek(seek_ts);
+ instance_.Seek(seek_ts);
if (seek_ts == 0) {
cache_at_zero_ = true;
}
@@ -1078,11 +721,6 @@ FFmpegFramePool::ElementPtr FFmpegDecoderInstance::RetrieveFrame(const int64_t&
}
}
- if (cache_is_locked) {
- cache_is_locked = false;
- } else if (unlocked) {
- cache_lock_.lock();
- }
if (ret == AVERROR_EOF) {
@@ -1095,53 +733,29 @@ FFmpegFramePool::ElementPtr FFmpegDecoderInstance::RetrieveFrame(const int64_t&
return_frame = cached_frames_.last();
}
- cache_wait_cond_.wakeAll();
- cache_lock_.unlock();
break;
} else {
- // Whatever it is, keep this frame in memory for the time being just in case
- if (!frame_pool_) {
- qCritical() << "Cannot retrieve video without a valid frame pool";
- cache_lock_.unlock();
- break;
+ // Cut down to thread count - 1 before we acquire a new frame
+ if (cached_frames_.size() == QThread::idealThreadCount()) {
+ RemoveFirstFrame();
}
- // Cut down to thread count - 1 before we acquire a new frame
- TruncateCacheRangeToFrames(QThread::idealThreadCount() -1);
-
- FFmpegFramePool::ElementPtr cached = frame_pool_->Get();
+ FFmpegFramePool::ElementPtr cached = pool_.Get();
if (!cached) {
qCritical() << "Frame pool failed to return a valid frame - out of memory?";
- cache_lock_.unlock();
break;
}
- {
- uint8_t* scale_data[4];
- int scale_linesize[4];
-
- av_image_fill_arrays(scale_data,
- scale_linesize,
- cached->data(),
- static_cast(working_frame.frame()->format),
- VideoParams::GetScaledDimension(working_frame.frame()->width, divider),
- VideoParams::GetScaledDimension(working_frame.frame()->height, divider),
- 1);
-
- sws_scale(scale_ctx_,
- working_frame.frame()->data,
- working_frame.frame()->linesize,
- 0,
- working_frame.frame()->height,
- scale_data,
- scale_linesize);
- }
+ // Store in queue, converting to native format
+ uint8_t* destination_data = cached->data();
+ int destination_linesize = Frame::generate_linesize_bytes(VideoParams::GetScaledDimension(instance_.avstream()->codecpar->width, divider), native_pix_fmt_, native_channel_count_);
+ FFmpegBufferToNativeBuffer(working_frame->data, working_frame->linesize, &destination_data, &destination_linesize);
// Set timestamp so this frame can be identified later
- cached->set_timestamp(working_frame.frame()->pts);
+ cached->set_timestamp(working_frame->pts);
// Store frame before just in case
FFmpegFramePool::ElementPtr previous;
@@ -1154,10 +768,6 @@ FFmpegFramePool::ElementPtr FFmpegDecoderInstance::RetrieveFrame(const int64_t&
// Append this frame and signal to other threads that a new frame has arrived
cached_frames_.append(cached);
- cache_wait_cond_.wakeAll();
- cache_lock_.unlock();
- unlocked = true;
-
// If this is a valid frame, see if this or the frame before it are the one we need
if (cached->timestamp() == target_ts) {
return_frame = cached;
@@ -1174,18 +784,12 @@ FFmpegFramePool::ElementPtr FFmpegDecoderInstance::RetrieveFrame(const int64_t&
}
}
+ av_frame_free(&working_frame);
av_packet_free(&pkt);
return return_frame;
}
-void FFmpegDecoder::ClearResources()
-{
- FreeScaler();
-
- open_ = false;
-}
-
void FFmpegDecoder::InitScaler(int divider)
{
VideoStream* vs = static_cast(stream().get());
@@ -1193,9 +797,9 @@ void FFmpegDecoder::InitScaler(int divider)
int scaled_width = VideoParams::GetScaledDimension(vs->width(), divider);
int scaled_height = VideoParams::GetScaledDimension(vs->height(), divider);
- scale_ctx_ = sws_getContext(scaled_width,
- scaled_height,
- src_pix_fmt_,
+ scale_ctx_ = sws_getContext(vs->width(),
+ vs->height(),
+ static_cast(instance_.avstream()->codecpar->format),
scaled_width,
scaled_height,
ideal_pix_fmt_,
@@ -1221,40 +825,7 @@ void FFmpegDecoder::FreeScaler()
}
}
-void FFmpegDecoderInstance::InitScaler(int divider)
-{
- int scaled_width = VideoParams::GetScaledDimension(avstream_->codecpar->width, divider);
- int scaled_height = VideoParams::GetScaledDimension(avstream_->codecpar->height, divider);
-
- scale_ctx_ = sws_getContext(avstream_->codecpar->width,
- avstream_->codecpar->height,
- static_cast(avstream_->codecpar->format),
- scaled_width,
- scaled_height,
- static_cast(avstream_->codecpar->format),
- SWS_FAST_BILINEAR,
- nullptr,
- nullptr,
- nullptr);
-
- if (scale_ctx_) {
- scale_divider_ = divider;
- } else {
- scale_divider_ = 0;
- }
-}
-
-void FFmpegDecoderInstance::FreeScaler()
-{
- if (scale_ctx_) {
- sws_freeContext(scale_ctx_);
- scale_ctx_ = nullptr;
-
- scale_divider_ = 0;
- }
-}
-
-int64_t FFmpegDecoderInstance::RangeStart() const
+/*int64_t FFmpegDecoder::RangeStart() const
{
if (cached_frames_.isEmpty()) {
return AV_NOPTS_VALUE;
@@ -1262,7 +833,7 @@ int64_t FFmpegDecoderInstance::RangeStart() const
return cached_frames_.first()->timestamp();
}
-int64_t FFmpegDecoderInstance::RangeEnd() const
+int64_t FFmpegDecoder::RangeEnd() const
{
if (cached_frames_.isEmpty()) {
return AV_NOPTS_VALUE;
@@ -1270,7 +841,7 @@ int64_t FFmpegDecoderInstance::RangeEnd() const
return cached_frames_.last()->timestamp();
}
-bool FFmpegDecoderInstance::CacheContainsTime(const int64_t &t) const
+bool FFmpegDecoder::CacheContainsTime(const int64_t &t) const
{
return !cached_frames_.isEmpty()
&& ((RangeStart() <= t && RangeEnd() >= t)
@@ -1278,22 +849,22 @@ bool FFmpegDecoderInstance::CacheContainsTime(const int64_t &t) const
|| (cache_at_eof_ && t > cached_frames_.last()->timestamp()));
}
-bool FFmpegDecoderInstance::CacheWillContainTime(const int64_t &t) const
+bool FFmpegDecoder::CacheWillContainTime(const int64_t &t) const
{
return !cached_frames_.isEmpty() && t >= cached_frames_.first()->timestamp() && t <= cache_target_time_;
}
-bool FFmpegDecoderInstance::CacheCouldContainTime(const int64_t &t) const
+bool FFmpegDecoder::CacheCouldContainTime(const int64_t &t) const
{
return !cached_frames_.isEmpty() && t >= cached_frames_.first()->timestamp() && t <= (cache_target_time_ + 2*second_ts_);
}
-bool FFmpegDecoderInstance::CacheIsEmpty() const
+bool FFmpegDecoder::CacheIsEmpty() const
{
return cached_frames_.isEmpty();
-}
+}*/
-FFmpegFramePool::ElementPtr FFmpegDecoderInstance::GetFrameFromCache(const int64_t &t) const
+FFmpegFramePool::ElementPtr FFmpegDecoder::GetFrameFromCache(const int64_t &t) const
{
if (t < cached_frames_.first()->timestamp()) {
@@ -1328,17 +899,14 @@ FFmpegFramePool::ElementPtr FFmpegDecoderInstance::GetFrameFromCache(const int64
return nullptr;
}
-void FFmpegDecoderInstance::RemoveFramesBefore(const qint64 &t)
+/*void FFmpegDecoder::RemoveFramesBefore(const qint64 &t)
{
- // We keep one frame in memory as an identifier for what pts the decoder is up to
- int min_frames = (MemoryPoolLimitReached() && !IsWorking()) ? 0 : 1;
-
- while (cached_frames_.size() > min_frames && cached_frames_.first()->last_accessed() < t) {
+ while (!cached_frames_.isEmpty() && cached_frames_.first()->last_accessed() < t) {
RemoveFirstFrame();
}
}
-int FFmpegDecoderInstance::TruncateCacheRangeToTime(const qint64 &t)
+int FFmpegDecoder::TruncateCacheRangeToTime(const qint64 &t)
{
int counter = 0;
@@ -1351,7 +919,7 @@ int FFmpegDecoderInstance::TruncateCacheRangeToTime(const qint64 &t)
return counter;
}
-int FFmpegDecoderInstance::TruncateCacheRangeToFrames(int nb_frames)
+int FFmpegDecoder::TruncateCacheRangeToFrames(int nb_frames)
{
int counter = 0;
@@ -1362,34 +930,30 @@ int FFmpegDecoderInstance::TruncateCacheRangeToFrames(int nb_frames)
}
return counter;
-}
+}*/
-void FFmpegDecoderInstance::RemoveFirstFrame()
+void FFmpegDecoder::RemoveFirstFrame()
{
cached_frames_.removeFirst();
cache_at_zero_ = false;
}
-FFmpegDecoderInstance::FFmpegDecoderInstance(const char *filename, int stream_index) :
+FFmpegDecoder::Instance::Instance() :
fmt_ctx_(nullptr),
codec_ctx_(nullptr),
- opts_(nullptr),
- scale_ctx_(nullptr),
- scale_divider_(0),
- frame_pool_(nullptr),
- is_working_(false),
- cache_at_zero_(false),
- cache_at_eof_(false),
- clear_timer_(nullptr)
+ opts_(nullptr)
+{
+}
+
+bool FFmpegDecoder::Instance::Open(const char *filename, int stream_index)
{
// Open file in a format context
int error_code = avformat_open_input(&fmt_ctx_, filename, nullptr, nullptr);
// Handle format context error
if (error_code != 0) {
- qCritical() << "Failed to open input:" << filename << error_code;
- ClearResources();
- return;
+ qCritical() << "Failed to open input:" << filename << FFmpegError(error_code);
+ return false;
}
// Get stream information from format
@@ -1397,9 +961,8 @@ FFmpegDecoderInstance::FFmpegDecoderInstance(const char *filename, int stream_in
// Handle get stream information error
if (error_code < 0) {
- qCritical() << "Failed to find stream info:" << error_code;
- ClearResources();
- return;
+ qCritical() << "Failed to find stream info:" << FFmpegError(error_code);
+ return false;
}
// Get reference to correct AVStream
@@ -1410,17 +973,18 @@ FFmpegDecoderInstance::FFmpegDecoderInstance(const char *filename, int stream_in
// Handle failure to find decoder
if (codec == nullptr) {
- qCritical() << "Failed to find appropriate decoder for this codec:" << filename << stream_index << avstream_->codecpar->codec_id;
- ClearResources();
- return;
+ qCritical() << "Failed to find appropriate decoder for this codec:"
+ << filename
+ << stream_index
+ << avstream_->codecpar->codec_id;
+ return false;
}
// Allocate context for the decoder
codec_ctx_ = avcodec_alloc_context3(codec);
if (codec_ctx_ == nullptr) {
qCritical() << "Failed to allocate codec context";
- ClearResources();
- return;
+ return false;
}
// Copy parameters from the AVStream to the AVCodecContext
@@ -1429,8 +993,7 @@ FFmpegDecoderInstance::FFmpegDecoderInstance(const char *filename, int stream_in
// Handle failure to copy parameters
if (error_code < 0) {
qCritical() << "Failed to copy parameters from AVStream to AVCodecContext";
- ClearResources();
- return;
+ return false;
}
// Set multithreading setting
@@ -1447,50 +1010,14 @@ FFmpegDecoderInstance::FFmpegDecoderInstance(const char *filename, int stream_in
char buf[50];
av_strerror(error_code, buf, 50);
qCritical() << "Failed to open codec" << codec->id << error_code << buf;
- ClearResources();
- return;
+ return false;
}
- // Create frame pool
- if (avstream_->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
- // Start clear timer
- clear_timer_ = new QTimer();
- clear_timer_->setInterval(kMaxFrameLife);
- clear_timer_->moveToThread(qApp->thread());
- connect(clear_timer_, &QTimer::timeout, this, &FFmpegDecoderInstance::ClearTimerEvent, Qt::DirectConnection);
- QMetaObject::invokeMethod(clear_timer_, "start", Qt::QueuedConnection);
- }
-
- // Store one second in the source's timebase
- second_ts_ = qRound64(av_q2d(av_inv_q(avstream_->time_base)));
+ return true;
}
-FFmpegDecoderInstance::~FFmpegDecoderInstance()
+void FFmpegDecoder::Instance::Close()
{
- ClearResources();
-}
-
-bool FFmpegDecoderInstance::IsValid() const
-{
- return codec_ctx_;
-}
-
-void FFmpegDecoderInstance::SetFramePool(FFmpegFramePool *frame_pool)
-{
- frame_pool_ = frame_pool;
-}
-
-void FFmpegDecoderInstance::ClearResources()
-{
- ClearFrameCache();
-
- // Stop timer
- if (clear_timer_) {
- QMetaObject::invokeMethod(clear_timer_, "stop", Qt::QueuedConnection);
- clear_timer_->deleteLater();
- clear_timer_ = nullptr;
- }
-
if (opts_) {
av_dict_free(&opts_);
opts_ = nullptr;
@@ -1505,20 +1032,57 @@ void FFmpegDecoderInstance::ClearResources()
avformat_close_input(&fmt_ctx_);
fmt_ctx_ = nullptr;
}
-
- FreeScaler();
}
-void FFmpegDecoderInstance::ClearTimerEvent()
+int FFmpegDecoder::Instance::GetFrame(AVPacket *pkt, AVFrame *frame)
{
- cache_lock()->lock();
- RemoveFramesBefore(QDateTime::currentMSecsSinceEpoch() - kMaxFrameLife);
- cache_lock()->unlock();
+ bool eof = false;
+
+ int ret;
+
+ // Clear any previous frames
+ av_frame_unref(frame);
+
+ while ((ret = avcodec_receive_frame(codec_ctx_, frame)) == AVERROR(EAGAIN) && !eof) {
+
+ // Find next packet in the correct stream index
+ do {
+ // Free buffer in packet if there is one
+ av_packet_unref(pkt);
+
+ // Read packet from file
+ ret = av_read_frame(fmt_ctx_, pkt);
+ } while (pkt->stream_index != avstream_->index && ret >= 0);
+
+ if (ret == AVERROR_EOF) {
+ // Don't break so that receive gets called again, but don't try to read again
+ eof = true;
+
+ // Send a null packet to signal end of
+ avcodec_send_packet(codec_ctx_, nullptr);
+ } else if (ret < 0) {
+ // Handle other error by breaking loop and returning the code we received
+ break;
+ } else {
+ // Successful read, send the packet
+ ret = avcodec_send_packet(codec_ctx_, pkt);
+
+ // We don't need the packet anymore, so free it
+ av_packet_unref(pkt);
+
+ if (ret < 0) {
+ break;
+ }
+ }
+ }
+
+ return ret;
}
-uint qHash(const FFmpegDecoder::FFmpegFramePoolKey &r)
+void FFmpegDecoder::Instance::Seek(int64_t timestamp)
{
- return ::qHash(r.width) ^ ::qHash(r.height) ^ ::qHash(r.format);
+ avcodec_flush_buffers(codec_ctx_);
+ av_seek_frame(fmt_ctx_, avstream_->index, timestamp, AVSEEK_FLAG_BACKWARD);
}
OLIVE_NAMESPACE_EXIT
diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h
index b2d91800f..1f820d746 100644
--- a/app/codec/ffmpeg/ffmpegdecoder.h
+++ b/app/codec/ffmpeg/ffmpegdecoder.h
@@ -32,7 +32,6 @@ extern "C" {
#include
#include
-#include "audio/sampleformat.h"
#include "avframeptr.h"
#include "codec/decoder.h"
#include "codec/waveoutput.h"
@@ -41,99 +40,6 @@ extern "C" {
OLIVE_NAMESPACE_ENTER
-class FFmpegDecoderInstance : public QObject {
- Q_OBJECT
-public:
- FFmpegDecoderInstance(const char* filename, int stream_index);
- virtual ~FFmpegDecoderInstance();
-
- DISABLE_COPY_MOVE(FFmpegDecoderInstance)
-
- bool IsValid() const;
-
- void SetFramePool(FFmpegFramePool* frame_pool);
-
- int64_t RangeStart() const;
- int64_t RangeEnd() const;
- bool CacheContainsTime(const int64_t& t) const;
- bool CacheWillContainTime(const int64_t& t) const;
- bool CacheCouldContainTime(const int64_t& t) const;
- bool CacheIsEmpty() const;
- FFmpegFramePool::ElementPtr GetFrameFromCache(const int64_t& t) const;
-
- void RemoveFramesBefore(const qint64& t);
- int TruncateCacheRangeToTime(const qint64& t);
- int TruncateCacheRangeToFrames(int nb_frames);
- void RemoveFirstFrame();
-
- AVFormatContext* fmt_ctx() const
- {
- return fmt_ctx_;
- }
-
- AVStream* stream() const
- {
- return avstream_;
- }
-
- void ClearFrameCache();
-
- FFmpegFramePool::ElementPtr RetrieveFrame(const int64_t &target_ts, int divider, bool cache_is_locked);
-
- /**
- * @brief Uses the FFmpeg API to retrieve a packet (stored in pkt_) and decode it (stored in frame_)
- *
- * @return
- *
- * An FFmpeg error code, or >= 0 on success
- */
- int GetFrame(AVPacket* pkt, AVFrame* frame);
-
- QMutex* cache_lock();
- QWaitCondition* cache_wait_cond();
-
- bool IsWorking();
- void SetWorking(bool working);
-
-private:
- void ClearResources();
-
- void Seek(int64_t timestamp);
-
- void InitScaler(int divider);
- void FreeScaler();
-
- AVFormatContext* fmt_ctx_;
- AVCodecContext* codec_ctx_;
- AVStream* avstream_;
- AVDictionary* opts_;
-
- SwsContext* scale_ctx_;
- int scale_divider_;
-
- int64_t second_ts_;
-
- QWaitCondition cache_wait_cond_;
- QMutex cache_lock_;
- QList cached_frames_;
- FFmpegFramePool* frame_pool_;
-
- int64_t cache_target_time_;
-
- bool is_working_;
- QMutex is_working_mutex_;
-
- bool cache_at_zero_;
- bool cache_at_eof_;
-
- QTimer* clear_timer_;
- static const int kMaxFrameLife;
-
-private slots:
- void ClearTimerEvent();
-
-};
-
/**
* @brief A Decoder derivative that wraps FFmpeg functions as on Olive decoder
*/
@@ -147,40 +53,62 @@ public:
// Destructor
virtual ~FFmpegDecoder() override;
- virtual FootagePtr Probe(const QString& filename, const QAtomicInt* cancelled) const override;
-
- virtual bool Open() override;
- virtual FramePtr RetrieveVideo(const rational &timecode, const int& divider) override;
- virtual SampleBufferPtr RetrieveAudio(const rational &timecode, const rational &length, const AudioParams& params) override;
- virtual void Close() override;
-
virtual QString id() override;
- virtual bool SupportsVideo() override;
- virtual bool SupportsAudio() override;
+ virtual bool SupportsVideo() override{return true;}
+ virtual bool SupportsAudio() override{return true;}
- virtual bool ConformAudio(const QAtomicInt* cancelled, const AudioParams& p) override;
+ virtual FootagePtr Probe(const QString& filename, const QAtomicInt* cancelled) const override;
- struct FFmpegFramePoolKey {
- int width;
- int height;
- AVPixelFormat format;
-
- bool operator==(const FFmpegFramePoolKey& k) const
- {
- return width == k.width && height == k.height && format == k.format;
- }
- };
+protected:
+ virtual bool OpenInternal() override;
+ virtual FramePtr RetrieveVideoInternal(const rational &timecode, const int& divider) override;
+ virtual bool ConformAudioInternal(const QString& filename, const AudioParams ¶ms, const QAtomicInt* cancelled) override;
+ virtual void CloseInternal() override;
private:
- /**
- * @brief Handle an error
- *
- * Immediately closes the Decoder (freeing memory resources) and sends the string provided to the warning stream.
- * As this function closes the Decoder, no further Decoder functions should be performed after this is called
- * (unless the Decoder is opened again first).
- */
- void Error(const QString& s);
+ class Instance
+ {
+ public:
+ Instance();
+
+ ~Instance()
+ {
+ Close();
+ }
+
+ bool Open(const char* filename, int stream_index);
+
+ void Close();
+
+ /**
+ * @brief Uses the FFmpeg API to retrieve a packet (stored in pkt_) and decode it (stored in frame_)
+ *
+ * @return
+ *
+ * An FFmpeg error code, or >= 0 on success
+ */
+ int GetFrame(AVPacket* pkt, AVFrame* frame);
+
+ void Seek(int64_t timestamp);
+
+ AVFormatContext* fmt_ctx() const
+ {
+ return fmt_ctx_;
+ }
+
+ AVStream* avstream() const
+ {
+ return avstream_;
+ }
+
+ private:
+ AVFormatContext* fmt_ctx_;
+ AVCodecContext* codec_ctx_;
+ AVStream* avstream_;
+ AVDictionary* opts_;
+
+ };
/**
* @brief Handle an FFmpeg error code
@@ -190,42 +118,50 @@ private:
*
* @param error_code
*/
- void FFmpegError(int error_code);
-
- void ClearResources();
+ static QString FFmpegError(int error_code);
void InitScaler(int divider);
void FreeScaler();
FramePtr RetrieveStillImage(const rational& timecode, const int& divider);
- static PixelFormat::Format GetNativePixelFormat(AVPixelFormat pix_fmt);
+ static VideoParams::Format GetNativePixelFormat(AVPixelFormat pix_fmt);
+ static int GetNativeChannelCount(AVPixelFormat pix_fmt);
static uint64_t ValidateChannelLayout(AVStream *stream);
- static bool StreamUsesMultipleInstances(StreamPtr stream);
+ void FFmpegBufferToNativeBuffer(uint8_t** input_data, int* input_linesize, uint8_t **output_buffer, int *output_linesize);
- FramePtr BuffersToNativeFrame(int divider, int width, int height, const rational &ts, uint8_t **input_data, int* input_linesize);
+ FFmpegFramePool::ElementPtr GetFrameFromCache(const int64_t& t) const;
+
+ void ClearFrameCache();
+
+ FFmpegFramePool::ElementPtr RetrieveFrame(const int64_t &target_ts, int divider);
+
+ void RemoveFirstFrame();
SwsContext* scale_ctx_;
int scale_divider_;
- AVPixelFormat src_pix_fmt_;
AVPixelFormat ideal_pix_fmt_;
- PixelFormat::Format native_pix_fmt_;
+ VideoParams::Format native_pix_fmt_;
+ int native_channel_count_;
- struct FFmpegFramePoolValue {
- FFmpegFramePool* pool = nullptr;
- int handles = 0;
- };
+ FFmpegFramePool pool_;
- static QHash< Stream*, QList > instance_map_;
- static QHash< FFmpegFramePoolKey, FFmpegFramePoolValue > frame_pool_map_;
- static QMutex instance_map_lock_;
+ int64_t second_ts_;
+
+ QList cached_frames_;
+
+ bool is_working_;
+ QMutex is_working_mutex_;
+
+ bool cache_at_zero_;
+ bool cache_at_eof_;
+
+ Instance instance_;
};
-uint qHash(const FFmpegDecoder::FFmpegFramePoolKey& r);
-
OLIVE_NAMESPACE_EXIT
#endif // FFMPEGDECODER_H
diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp
index c31ac6b5a..3e95937db 100644
--- a/app/codec/ffmpeg/ffmpegencoder.cpp
+++ b/app/codec/ffmpeg/ffmpegencoder.cpp
@@ -26,8 +26,7 @@ extern "C" {
#include
-#include "ffmpegcommon.h"
-#include "render/pixelformat.h"
+#include "common/ffmpegutils.h"
OLIVE_NAMESPACE_ENTER
@@ -36,7 +35,8 @@ FFmpegEncoder::FFmpegEncoder(const EncodingParams ¶ms) :
fmt_ctx_(nullptr),
video_stream_(nullptr),
video_codec_ctx_(nullptr),
- video_scale_ctx_(nullptr),
+ video_alpha_scale_ctx_(nullptr),
+ video_noalpha_scale_ctx_(nullptr),
audio_stream_(nullptr),
audio_codec_ctx_(nullptr),
audio_resample_ctx_(nullptr),
@@ -72,29 +72,49 @@ bool FFmpegEncoder::Open()
}
// This is the format we will expect frames received in Write() to be in
- PixelFormat::Format native_pixel_fmt = params().video_params().format();
+ VideoParams::Format native_pixel_fmt = params().video_params().format();
// This is the format we will need to convert the frame to for swscale to understand it
- video_conversion_fmt_ = FFmpegCommon::GetCompatiblePixelFormat(native_pixel_fmt);
+ video_conversion_fmt_ = FFmpegUtils::GetCompatiblePixelFormat(native_pixel_fmt);
// This is the equivalent pixel format above as an AVPixelFormat that swscale can understand
- AVPixelFormat src_pix_fmt = FFmpegCommon::GetFFmpegPixelFormat(video_conversion_fmt_);
+ AVPixelFormat src_alpha_pix_fmt = FFmpegUtils::GetFFmpegPixelFormat(video_conversion_fmt_,
+ VideoParams::kRGBAChannelCount);
+
+ AVPixelFormat src_noalpha_pix_fmt = FFmpegUtils::GetFFmpegPixelFormat(video_conversion_fmt_,
+ VideoParams::kRGBChannelCount);
+
+ if (src_alpha_pix_fmt == AV_PIX_FMT_NONE || src_noalpha_pix_fmt == AV_PIX_FMT_NONE) {
+ Error(QStringLiteral("Failed to find suitable pixel format for this buffer"));
+ return false;
+ }
// This is the pixel format the encoder wants to encode to
AVPixelFormat encoder_pix_fmt = video_codec_ctx_->pix_fmt;
// Set up a scaling context - if the native pixel format is not equal to the encoder's, we'll need to convert it
// before encoding. Even if we don't, this may be useful for converting between linesizes, etc.
- video_scale_ctx_ = sws_getContext(params().video_params().width(),
- params().video_params().height(),
- src_pix_fmt,
- params().video_params().width(),
- params().video_params().height(),
- encoder_pix_fmt,
- 0,
- nullptr,
- nullptr,
- nullptr);
+ video_alpha_scale_ctx_ = sws_getContext(params().video_params().width(),
+ params().video_params().height(),
+ src_alpha_pix_fmt,
+ params().video_params().width(),
+ params().video_params().height(),
+ encoder_pix_fmt,
+ 0,
+ nullptr,
+ nullptr,
+ nullptr);
+
+ video_noalpha_scale_ctx_ = sws_getContext(params().video_params().width(),
+ params().video_params().height(),
+ src_noalpha_pix_fmt,
+ params().video_params().width(),
+ params().video_params().height(),
+ encoder_pix_fmt,
+ 0,
+ nullptr,
+ nullptr,
+ nullptr);
}
// Initialize an audio stream if it's enabled
@@ -157,19 +177,22 @@ bool FFmpegEncoder::WriteFrame(FramePtr frame, rational time)
// We may need to convert this frame to a frame that swscale will understand
if (frame->format() != video_conversion_fmt_) {
- frame = PixelFormat::ConvertPixelFormat(frame, video_conversion_fmt_);
+ frame = frame->convert(video_conversion_fmt_);
}
// Use swscale context to convert formats/linesizes
input_data = frame->const_data();
input_linesize = frame->linesize_bytes();
- error_code = sws_scale(video_scale_ctx_,
+
+ error_code = sws_scale((frame->channel_count() == VideoParams::kRGBAChannelCount) ? video_alpha_scale_ctx_ : video_noalpha_scale_ctx_,
reinterpret_cast(&input_data),
&input_linesize,
0,
frame->height(),
encoded_frame->data,
encoded_frame->linesize);
+
+
if (error_code < 0) {
FFmpegError("Failed to scale frame", error_code);
goto fail;
@@ -208,7 +231,7 @@ void FFmpegEncoder::WriteAudio(AudioParams pcm_info, QIODevice* file)
audio_codec_ctx_->sample_fmt,
audio_codec_ctx_->sample_rate,
static_cast(pcm_info.channel_layout()),
- FFmpegCommon::GetFFmpegSampleFormat(pcm_info.format()),
+ FFmpegUtils::GetFFmpegSampleFormat(pcm_info.format()),
pcm_info.sample_rate(),
0,
nullptr);
@@ -300,9 +323,14 @@ void FFmpegEncoder::Close()
open_ = false;
}
- if (video_scale_ctx_) {
- sws_freeContext(video_scale_ctx_);
- video_scale_ctx_ = nullptr;
+ if (video_alpha_scale_ctx_) {
+ sws_freeContext(video_alpha_scale_ctx_);
+ video_alpha_scale_ctx_ = nullptr;
+ }
+
+ if (video_noalpha_scale_ctx_) {
+ sws_freeContext(video_noalpha_scale_ctx_);
+ video_noalpha_scale_ctx_ = nullptr;
}
if (video_codec_ctx_) {
diff --git a/app/codec/ffmpeg/ffmpegencoder.h b/app/codec/ffmpeg/ffmpegencoder.h
index 4efc76754..6b28fb62f 100644
--- a/app/codec/ffmpeg/ffmpegencoder.h
+++ b/app/codec/ffmpeg/ffmpegencoder.h
@@ -47,6 +47,11 @@ public:
virtual void Close() override;
+ virtual VideoParams::Format GetDesiredPixelFormat() const override
+ {
+ return video_conversion_fmt_;
+ }
+
private:
/**
* @brief Handle an error
@@ -80,8 +85,9 @@ private:
AVStream* video_stream_;
AVCodecContext* video_codec_ctx_;
- SwsContext* video_scale_ctx_;
- PixelFormat::Format video_conversion_fmt_;
+ SwsContext* video_alpha_scale_ctx_;
+ SwsContext* video_noalpha_scale_ctx_;
+ VideoParams::Format video_conversion_fmt_;
AVStream* audio_stream_;
AVCodecContext* audio_codec_ctx_;
diff --git a/app/codec/ffmpeg/ffmpegframepool.cpp b/app/codec/ffmpeg/ffmpegframepool.cpp
index cca05aed4..520f5d0ac 100644
--- a/app/codec/ffmpeg/ffmpegframepool.cpp
+++ b/app/codec/ffmpeg/ffmpegframepool.cpp
@@ -20,9 +20,7 @@
#include "ffmpegframepool.h"
-extern "C" {
-#include
-}
+#include "codec/frame.h"
OLIVE_NAMESPACE_ENTER
@@ -30,32 +28,24 @@ FFmpegFramePool::FFmpegFramePool(int element_count) :
MemoryPool(element_count),
width_(0),
height_(0),
- format_(AV_PIX_FMT_NONE)
+ format_(VideoParams::kFormatInvalid),
+ channel_count_(0)
{
}
-void FFmpegFramePool::SetParameters(int width, int height, AVPixelFormat format)
+void FFmpegFramePool::SetParameters(int width, int height, VideoParams::Format format, int channel_count)
{
Clear();
width_ = width;
height_ = height;
format_ = format;
+ channel_count_ = channel_count;
}
size_t FFmpegFramePool::GetElementSize()
{
- int buf_sz = av_image_get_buffer_size(static_cast(format_),
- width_,
- height_,
- 1);
-
- if (buf_sz < 0) {
- qDebug() << "Failed to find buffer size:" << buf_sz;
- return 0;
- }
-
- return buf_sz;
+ return Frame::generate_linesize_bytes(width_, format_, channel_count_) * height_;
}
OLIVE_NAMESPACE_EXIT
diff --git a/app/codec/ffmpeg/ffmpegframepool.h b/app/codec/ffmpeg/ffmpegframepool.h
index 81a31312e..f97a948d0 100644
--- a/app/codec/ffmpeg/ffmpegframepool.h
+++ b/app/codec/ffmpeg/ffmpegframepool.h
@@ -22,7 +22,6 @@
#define FFMPEGFRAMEPOOL_H
#include "common/memorypool.h"
-#include "render/pixelformat.h"
#include "render/videoparams.h"
OLIVE_NAMESPACE_ENTER
@@ -32,7 +31,7 @@ class FFmpegFramePool : public MemoryPool
public:
FFmpegFramePool(int element_count);
- void SetParameters(int width, int height, AVPixelFormat format);
+ void SetParameters(int width, int height, VideoParams::Format format, int channel_count);
const int& width() const
{
@@ -52,7 +51,9 @@ private:
int height_;
- AVPixelFormat format_;
+ VideoParams::Format format_;
+
+ int channel_count_;
};
diff --git a/app/codec/frame.cpp b/app/codec/frame.cpp
index 8cd0a53bd..7d474a1c5 100644
--- a/app/codec/frame.cpp
+++ b/app/codec/frame.cpp
@@ -20,10 +20,13 @@
#include "frame.h"
+#include
#include
#include
#include
+#include "common/oiioutils.h"
+
OLIVE_NAMESPACE_ENTER
Frame::Frame() :
@@ -45,33 +48,14 @@ void Frame::set_video_params(const VideoParams ¶ms)
{
params_ = params;
- // Align linesize to 32
- linesize_ = qCeil(static_cast(width()) / 32.0) * 32;
+ linesize_ = generate_linesize_bytes(width(), params_.format(), params_.channel_count());
+ linesize_pixels_ = linesize_ / params_.GetBytesPerPixel();
}
-int Frame::linesize_pixels() const
+int Frame::generate_linesize_bytes(int width, VideoParams::Format format, int channel_count)
{
- return linesize_;
-}
-
-int Frame::linesize_bytes() const
-{
- return linesize_pixels() * PixelFormat::BytesPerPixel(params_.format());
-}
-
-const int &Frame::width() const
-{
- return params_.effective_width();
-}
-
-const int &Frame::height() const
-{
- return params_.effective_height();
-}
-
-const PixelFormat::Format &Frame::format() const
-{
- return params_.format();
+ // Align to 32 bytes (not sure if this is necessary?)
+ return VideoParams::GetBytesPerPixel(format, channel_count) * ((width + 31) & ~31);
}
Color Frame::get_pixel(int x, int y) const
@@ -80,11 +64,9 @@ Color Frame::get_pixel(int x, int y) const
return Color();
}
- int pixel_index = y * linesize_pixels() + x;
+ int byte_offset = y * linesize_bytes() + x * video_params().GetBytesPerPixel();
- int byte_offset = PixelFormat::GetBufferSize(video_params().format(), pixel_index, 1);
-
- return Color(data_.data() + byte_offset, video_params().format());
+ return Color(data_.data() + byte_offset, video_params().format(), video_params().channel_count());
}
bool Frame::contains_pixel(int x, int y) const
@@ -98,57 +80,53 @@ void Frame::set_pixel(int x, int y, const Color &c)
return;
}
- int pixel_index = y * linesize_pixels() + x;
+ int byte_offset = y * linesize_bytes() + x * video_params().GetBytesPerPixel();
- int byte_offset = PixelFormat::GetBufferSize(video_params().format(), pixel_index, 1);
-
- c.toData(data_.data() + byte_offset, video_params().format());
+ c.toData(data_.data() + byte_offset, video_params().format(), video_params().channel_count());
}
-const rational &Frame::timestamp() const
-{
- return timestamp_;
-}
-
-void Frame::set_timestamp(const rational ×tamp)
-{
- timestamp_ = timestamp;
-}
-
-char *Frame::data()
-{
- return data_.data();
-}
-
-const char *Frame::const_data() const
-{
- return data_.constData();
-}
-
-void Frame::allocate()
+bool Frame::allocate()
{
// Assume this frame is intended to be a video frame
if (!params_.is_valid()) {
qWarning() << "Tried to allocate a frame with invalid parameters";
- return;
+ return false;
}
- data_.resize(PixelFormat::GetBufferSize(params_.format(), linesize_, params_.height()));
+ data_.resize(VideoParams::GetBufferSize(linesize_, height(), params_.format(), params_.channel_count()));
+
+ return true;
}
-bool Frame::is_allocated() const
+FramePtr Frame::convert(VideoParams::Format format) const
{
- return !data_.isEmpty();
-}
+ // Create new params with destination format
+ VideoParams params = params_;
+ params.set_format(format);
-void Frame::destroy()
-{
- data_.clear();
-}
+ // Create new frame
+ FramePtr converted = Frame::Create();
+ converted->set_video_params(params);
+ converted->set_timestamp(timestamp_);
+ converted->allocate();
-int Frame::allocated_size() const
-{
- return data_.size();
+ // Do the conversion through OIIO for convenience
+ OIIO::ImageBuf src(OIIO::ImageSpec(width(), height(),
+ channel_count(),
+ OIIOUtils::GetOIIOBaseTypeFromFormat(this->format())));
+
+ OIIOUtils::FrameToBuffer(this, &src);
+
+ OIIO::ImageBuf dst(OIIO::ImageSpec(converted->width(), converted->height(),
+ channel_count(),
+ OIIOUtils::GetOIIOBaseTypeFromFormat(format)));
+
+ if (dst.copy_pixels(src)) {
+ OIIOUtils::BufferToFrame(&dst, converted.get());
+ return converted;
+ } else {
+ return nullptr;
+ }
}
OLIVE_NAMESPACE_EXIT
diff --git a/app/codec/frame.h b/app/codec/frame.h
index 7618ad622..29fbdaac1 100644
--- a/app/codec/frame.h
+++ b/app/codec/frame.h
@@ -26,7 +26,6 @@
#include "common/rational.h"
#include "render/color.h"
-#include "render/pixelformat.h"
#include "render/videoparams.h"
OLIVE_NAMESPACE_ENTER
@@ -47,11 +46,37 @@ public:
const VideoParams& video_params() const;
void set_video_params(const VideoParams& params);
- int linesize_pixels() const;
- int linesize_bytes() const;
- const int& width() const;
- const int& height() const;
- const PixelFormat::Format& format() const;
+ static int generate_linesize_bytes(int width, VideoParams::Format format, int channel_count);
+
+ int linesize_pixels() const
+ {
+ return linesize_pixels_;
+ }
+
+ int linesize_bytes() const
+ {
+ return linesize_;
+ }
+
+ int width() const
+ {
+ return params_.effective_width();
+ }
+
+ int height() const
+ {
+ return params_.effective_height();
+ }
+
+ VideoParams::Format format() const
+ {
+ return params_.format();
+ }
+
+ int channel_count() const
+ {
+ return params_.channel_count();
+ }
Color get_pixel(int x, int y) const;
bool contains_pixel(int x, int y) const;
@@ -62,18 +87,31 @@ public:
*
* This timestamp is always a rational that will equate to the time in seconds.
*/
- const rational& timestamp() const;
- void set_timestamp(const rational& timestamp);
+ const rational& timestamp() const
+ {
+ return timestamp_;
+ }
+
+ void set_timestamp(const rational& timestamp)
+ {
+ timestamp_ = timestamp;
+ }
/**
* @brief Get the data buffer of this frame
*/
- char* data();
+ char* data()
+ {
+ return data_.data();
+ }
/**
* @brief Get the const data buffer of this frame
*/
- const char* const_data() const;
+ const char* const_data() const
+ {
+ return data_.constData();
+ }
/**
* @brief Allocate memory buffer to store data based on parameters
@@ -82,24 +120,35 @@ public:
*
* If a memory buffer has been previously allocated without destroying, this function will destroy it.
*/
- void allocate();
+ bool allocate();
/**
* @brief Return whether the frame is allocated or not
*/
- bool is_allocated() const;
+ bool is_allocated() const
+ {
+ return !data_.isEmpty();
+ }
/**
* @brief Destroy a memory buffer allocated with allocate()
*/
- void destroy();
+ void destroy()
+ {
+ data_.clear();
+ }
/**
* @brief Returns the size of the array returned in data() in bytes
*
* Returns 0 if nothing is allocated.
*/
- int allocated_size() const;
+ int allocated_size() const
+ {
+ return data_.size();
+ }
+
+ FramePtr convert(VideoParams::Format format) const;
private:
VideoParams params_;
@@ -110,6 +159,8 @@ private:
int linesize_;
+ int linesize_pixels_;
+
};
OLIVE_NAMESPACE_EXIT
diff --git a/app/codec/oiio/CMakeLists.txt b/app/codec/oiio/CMakeLists.txt
index 19103a192..201fd3ee7 100644
--- a/app/codec/oiio/CMakeLists.txt
+++ b/app/codec/oiio/CMakeLists.txt
@@ -16,7 +16,7 @@
set(OLIVE_SOURCES
${OLIVE_SOURCES}
- codec/oiio/oiiodecoder.h
codec/oiio/oiiodecoder.cpp
+ codec/oiio/oiiodecoder.h
PARENT_SCOPE
)
diff --git a/app/codec/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp
index 4ddbf0ca5..392951240 100644
--- a/app/codec/oiio/oiiodecoder.cpp
+++ b/app/codec/oiio/oiiodecoder.cpp
@@ -27,6 +27,7 @@
#include
#include "common/define.h"
+#include "common/oiioutils.h"
#include "config/config.h"
#include "core.h"
@@ -40,6 +41,11 @@ OIIODecoder::OIIODecoder() :
{
}
+OIIODecoder::~OIIODecoder()
+{
+ CloseInternal();
+}
+
QString OIIODecoder::id()
{
return QStringLiteral("oiio");
@@ -47,6 +53,10 @@ QString OIIODecoder::id()
FootagePtr OIIODecoder::Probe(const QString& filename, const QAtomicInt* cancelled) const
{
+ Q_UNUSED(cancelled)
+
+ // Filter out any file extensions that aren't expected to work - sometimes OIIO will crash trying
+ // to open a file that it can't if it's given one
if (!FileTypeIsSupported(filename)) {
return nullptr;
}
@@ -59,8 +69,9 @@ FootagePtr OIIODecoder::Probe(const QString& filename, const QAtomicInt* cancell
return nullptr;
}
+ // Filter out OIIO detecting an "FFmpeg movie", we have a native FFmpeg decoder that can handle
+ // it better
if (!strcmp(in->format_name(), "FFmpeg movie")) {
- // If this is FFmpeg via OIIO, fall-through to our native FFmpeg decoder
return nullptr;
}
@@ -70,8 +81,9 @@ FootagePtr OIIODecoder::Probe(const QString& filename, const QAtomicInt* cancell
image_stream->set_width(in->spec().width);
image_stream->set_height(in->spec().height);
- image_stream->set_format(GetFormatFromOIIOBasetype(in->spec()));
- image_stream->set_pixel_aspect_ratio(GetPixelAspectRatioFromOIIO(in->spec()));
+ image_stream->set_format(OIIOUtils::GetFormatFromOIIOBasetype(static_cast(in->spec().format.basetype)));
+ image_stream->set_channel_count(in->spec().nchannels);
+ image_stream->set_pixel_aspect_ratio(OIIOUtils::GetPixelAspectRatioFromOIIO(in->spec()));
image_stream->set_video_type(VideoStream::kVideoTypeStill);
// Images will always have just one stream
@@ -95,45 +107,40 @@ FootagePtr OIIODecoder::Probe(const QString& filename, const QAtomicInt* cancell
return footage;
}
-bool OIIODecoder::Open()
+bool OIIODecoder::OpenInternal()
{
- Q_ASSERT(stream());
+ // If we can open the filename provided, assume everything is working (even if this is an image
+ // sequence with potentially missing frame)
+ if (OpenImageHandler(stream()->footage()->filename())) {
+ VideoStreamPtr video_stream = std::static_pointer_cast(stream());
- if (stream()->type() != Stream::kVideo) {
- // Guard against non-video types
- return false;
+ if (video_stream->video_type() == VideoStream::kVideoTypeStill) {
+ last_sequence_index_ = 0;
+ } else {
+ last_sequence_index_ = GetImageSequenceIndex(stream()->footage()->filename());
+ }
+
+ return true;
}
-
- VideoStreamPtr video_stream = std::static_pointer_cast(stream());
-
- if (video_stream->video_type() == VideoStream::kVideoTypeVideo) {
- // This decoder only handles kVideoTypeImageSequence and kVideoTypeStill
- return false;
- }
-
- if (video_stream->video_type() == VideoStream::kVideoTypeStill
- && !OpenImageHandler(stream()->footage()->filename())) {
- return false;
- }
-
- open_ = true;
-
- return true;
+ return false;
}
-FramePtr OIIODecoder::RetrieveVideo(const rational &timecode, const int& divider)
+FramePtr OIIODecoder::RetrieveVideoInternal(const rational &timecode, const int& divider)
{
- if (!open_) {
- qWarning() << "Tried to retrieve video on a decoder that's still closed";
- return nullptr;
- }
-
VideoStreamPtr video_stream = std::static_pointer_cast(stream());
- if (video_stream->video_type() == VideoStream::kVideoTypeImageSequence) {
- int64_t ts = video_stream->get_time_in_timebase_units(timecode);
+ int64_t sequence_index;
- if (!OpenImageHandler(TransformImageSequenceFileName(stream()->footage()->filename(), ts))) {
+ if (video_stream->video_type() == VideoStream::kVideoTypeStill) {
+ sequence_index = 0;
+ } else {
+ sequence_index = video_stream->get_time_in_timebase_units(timecode);
+ }
+
+ if (last_sequence_index_ != sequence_index) {
+ CloseImageHandle();
+
+ if (!OpenImageHandler(TransformImageSequenceFileName(stream()->footage()->filename(), sequence_index))) {
return nullptr;
}
}
@@ -143,14 +150,15 @@ FramePtr OIIODecoder::RetrieveVideo(const rational &timecode, const int& divider
frame->set_video_params(VideoParams(buffer_->spec().width,
buffer_->spec().height,
pix_fmt_,
- GetPixelAspectRatioFromOIIO(buffer_->spec()),
+ channel_count_,
+ OIIOUtils::GetPixelAspectRatioFromOIIO(buffer_->spec()),
VideoParams::kInterlaceNone, // FIXME: Does OIIO deinterlace for us?
divider));
frame->allocate();
if (divider == 1) {
- BufferToFrame(buffer_, frame);
+ OIIOUtils::BufferToFrame(buffer_, frame.get());
} else {
@@ -161,106 +169,18 @@ FramePtr OIIODecoder::RetrieveVideo(const rational &timecode, const int& divider
qWarning() << "OIIO resize failed";
}
- BufferToFrame(&dst, frame);
+ OIIOUtils::BufferToFrame(&dst, frame.get());
}
- if (video_stream->video_type() == VideoStream::kVideoTypeImageSequence) {
- CloseImageHandle();
- }
-
return frame;
}
-void OIIODecoder::Close()
+void OIIODecoder::CloseInternal()
{
CloseImageHandle();
}
-bool OIIODecoder::SupportsVideo()
-{
- return true;
-}
-
-void OIIODecoder::FrameToBuffer(FramePtr frame, OIIO::ImageBuf *buf)
-{
-#if OIIO_VERSION < 20112
- //
- // Workaround for OIIO bug that ignores destination stride in versions OLDER than 2.1.12
- //
- // See more: https://github.com/OpenImageIO/oiio/pull/2487
- //
- int width_in_bytes = frame->width() * PixelFormat::BytesPerPixel(frame->format());
-
- for (int i=0;ispec().height;i++) {
- memcpy(
-#if OIIO_VERSION < 10903
- reinterpret_cast(buf->localpixels()) + i * width_in_bytes,
-#else
- reinterpret_cast(buf->localpixels()) + i * buf->scanline_stride(),
-#endif
- frame->data() + i * frame->linesize_bytes(),
- width_in_bytes);
- }
-#else
- buf->set_pixels(OIIO::ROI(),
- buf->spec().format,
- frame->data(),
- OIIO::AutoStride,
- frame->linesize_bytes());
-#endif
-}
-
-void OIIODecoder::BufferToFrame(OIIO::ImageBuf *buf, FramePtr frame)
-{
-#if OIIO_VERSION < 20112
- //
- // Workaround for OIIO bug that ignores destination stride in versions OLDER than 2.1.12
- //
- // See more: https://github.com/OpenImageIO/oiio/pull/2487
- //
- int width_in_bytes = frame->width() * PixelFormat::BytesPerPixel(frame->format());
-
- for (int i=0;ispec().height;i++) {
- memcpy(frame->data() + i * frame->linesize_bytes(),
-#if OIIO_VERSION < 10903
- reinterpret_cast(buf->localpixels()) + i * width_in_bytes,
-#else
- reinterpret_cast(buf->localpixels()) + i * buf->scanline_stride(),
-#endif
- width_in_bytes);
- }
-#else
- buf->get_pixels(OIIO::ROI(),
- buf->spec().format,
- frame->data(),
- OIIO::AutoStride,
- frame->linesize_bytes());
-#endif
-}
-
-PixelFormat::Format OIIODecoder::GetFormatFromOIIOBasetype(const OIIO::ImageSpec& spec)
-{
- bool has_alpha = (spec.nchannels == kRGBAChannels);
-
- if (spec.format == OIIO::TypeDesc::UINT8) {
- return has_alpha ? PixelFormat::PIX_FMT_RGBA8 : PixelFormat::PIX_FMT_RGB8;
- } else if (spec.format == OIIO::TypeDesc::UINT16) {
- return has_alpha ? PixelFormat::PIX_FMT_RGBA16U : PixelFormat::PIX_FMT_RGB16U;
- } else if (spec.format == OIIO::TypeDesc::HALF) {
- return has_alpha ? PixelFormat::PIX_FMT_RGBA16F : PixelFormat::PIX_FMT_RGB16F;
- } else if (spec.format == OIIO::TypeDesc::FLOAT) {
- return has_alpha ? PixelFormat::PIX_FMT_RGBA32F : PixelFormat::PIX_FMT_RGB32F;
- } else {
- return PixelFormat::PIX_FMT_INVALID;
- }
-}
-
-rational OIIODecoder::GetPixelAspectRatioFromOIIO(const OIIO::ImageSpec &spec)
-{
- return rational::fromDouble(spec.get_float_attribute("PixelAspectRatio", 1));
-}
-
bool OIIODecoder::FileTypeIsSupported(const QString& fn)
{
// We prioritize OIIO over FFmpeg to pick up still images more effectively, but some OIIO decoders (notably OpenJPEG)
@@ -297,17 +217,23 @@ bool OIIODecoder::OpenImageHandler(const QString &fn)
// Check if we can work with this pixel format
const OIIO::ImageSpec& spec = image_->spec();
- is_rgba_ = (spec.nchannels == kRGBAChannels);
+ // Store channel count
+ channel_count_ = spec.nchannels;
- pix_fmt_ = GetFormatFromOIIOBasetype(spec);
+ // We use RGBA frames because that tends to be the native format of GPUs
+ pix_fmt_ = OIIOUtils::GetFormatFromOIIOBasetype(static_cast(spec.format.basetype));
- if (pix_fmt_ == PixelFormat::PIX_FMT_INVALID) {
+ if (pix_fmt_ == VideoParams::kFormatInvalid) {
qWarning() << "Failed to convert OIIO::ImageDesc to native pixel format";
return false;
}
- // FIXME: Many OIIO pixel formats are not handled here
- OIIO::TypeDesc type = PixelFormat::GetOIIOTypeDesc(pix_fmt_);
+ OIIO::TypeDesc::BASETYPE type = OIIOUtils::GetOIIOBaseTypeFromFormat(pix_fmt_);
+
+ if (type == OIIO::TypeDesc::UNKNOWN) {
+ qCritical() << "Failed to determine appropriate OIIO basetype from native format";
+ return false;
+ }
#if OIIO_VERSION < 20100
buffer_ = new OIIO::ImageBuf(OIIO::ImageSpec(spec.width, spec.height, spec.nchannels, type));
diff --git a/app/codec/oiio/oiiodecoder.h b/app/codec/oiio/oiiodecoder.h
index 11e7638ab..f4b7e96d9 100644
--- a/app/codec/oiio/oiiodecoder.h
+++ b/app/codec/oiio/oiiodecoder.h
@@ -25,7 +25,6 @@
#include
#include "codec/decoder.h"
-#include "render/pixelformat.h"
OLIVE_NAMESPACE_ENTER
@@ -35,23 +34,18 @@ class OIIODecoder : public Decoder
public:
OIIODecoder();
+ virtual ~OIIODecoder() override;
+
virtual QString id() override;
+ virtual bool SupportsVideo() override{return true;}
+
virtual FootagePtr Probe(const QString& filename, const QAtomicInt* cancelled) const override;
- virtual bool Open() override;
- virtual FramePtr RetrieveVideo(const rational &timecode, const int& divider) override;
- virtual void Close() override;
-
- virtual bool SupportsVideo() override;
-
- static void FrameToBuffer(FramePtr frame, OIIO::ImageBuf* buf);
-
- static void BufferToFrame(OIIO::ImageBuf* buf, FramePtr frame);
-
- static PixelFormat::Format GetFormatFromOIIOBasetype(const OIIO::ImageSpec& spec);
-
- static rational GetPixelAspectRatioFromOIIO(const OIIO::ImageSpec& spec);
+protected:
+ virtual bool OpenInternal() override;
+ virtual FramePtr RetrieveVideoInternal(const rational &timecode, const int& divider) override;
+ virtual void CloseInternal() override;
private:
#if OIIO_VERSION < 10903
@@ -66,9 +60,11 @@ private:
void CloseImageHandle();
- PixelFormat::Format pix_fmt_;
+ int64_t last_sequence_index_;
- bool is_rgba_;
+ VideoParams::Format pix_fmt_;
+
+ int channel_count_;
OIIO::ImageBuf* buffer_;
diff --git a/app/codec/samplebuffer.cpp b/app/codec/samplebuffer.cpp
index fdd25b825..8c8fdc3b9 100644
--- a/app/codec/samplebuffer.cpp
+++ b/app/codec/samplebuffer.cpp
@@ -38,6 +38,11 @@ SampleBufferPtr SampleBuffer::Create()
return std::make_shared();
}
+SampleBufferPtr SampleBuffer::CreateAllocated(const AudioParams &audio_params, const rational &length)
+{
+ return CreateAllocated(audio_params, audio_params.time_to_samples(length));
+}
+
SampleBufferPtr SampleBuffer::CreateAllocated(const AudioParams &audio_params, int samples_per_channel)
{
SampleBufferPtr buffer = Create();
diff --git a/app/codec/samplebuffer.h b/app/codec/samplebuffer.h
index 891e9156d..eef51ff8a 100644
--- a/app/codec/samplebuffer.h
+++ b/app/codec/samplebuffer.h
@@ -46,6 +46,7 @@ public:
virtual ~SampleBuffer();
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);
diff --git a/app/codec/waveinput.cpp b/app/codec/waveinput.cpp
index 275bea1f2..06fd5f2b6 100644
--- a/app/codec/waveinput.cpp
+++ b/app/codec/waveinput.cpp
@@ -108,27 +108,27 @@ bool WaveInput::open()
uint16_t bits_per_sample;
data_stream >> bits_per_sample;
- SampleFormat::Format format;
+ AudioParams::Format format;
switch (bits_per_sample) {
case 8:
- format = SampleFormat::SAMPLE_FMT_U8;
+ format = AudioParams::kFormatUnsigned8;
break;
case 16:
- format = SampleFormat::SAMPLE_FMT_S16;
+ format = AudioParams::kFormatSigned16;
break;
case 32:
if (data_is_float) {
- format = SampleFormat::SAMPLE_FMT_FLT;
+ format = AudioParams::kFormatFloat32;
} else {
- format = SampleFormat::SAMPLE_FMT_S32;
+ format = AudioParams::kFormatSigned32;
}
break;
case 64:
if (data_is_float) {
- format = SampleFormat::SAMPLE_FMT_DBL;
+ format = AudioParams::kFormatFloat64;
} else {
- format = SampleFormat::SAMPLE_FMT_S64;
+ format = AudioParams::kFormatSigned64;
}
break;
default:
diff --git a/app/codec/waveoutput.cpp b/app/codec/waveoutput.cpp
index 844b3ad8f..648b8c4a8 100644
--- a/app/codec/waveoutput.cpp
+++ b/app/codec/waveoutput.cpp
@@ -20,6 +20,8 @@
#include "waveoutput.h"
+#include "render/audioparams.h"
+
OLIVE_NAMESPACE_ENTER
const int16_t kWAVIntegerFormat = 1;
@@ -60,18 +62,18 @@ bool WaveOutput::open()
// Type of format
switch (params_.format()) {
- case SampleFormat::SAMPLE_FMT_U8:
- case SampleFormat::SAMPLE_FMT_S16:
- case SampleFormat::SAMPLE_FMT_S32:
- case SampleFormat::SAMPLE_FMT_S64:
+ case AudioParams::kFormatUnsigned8:
+ case AudioParams::kFormatSigned16:
+ case AudioParams::kFormatSigned32:
+ case AudioParams::kFormatSigned64:
write_int(&file_, kWAVIntegerFormat);
break;
- case SampleFormat::SAMPLE_FMT_FLT:
- case SampleFormat::SAMPLE_FMT_DBL:
+ case AudioParams::kFormatFloat32:
+ case AudioParams::kFormatFloat64:
write_int(&file_, kWAVFloatFormat);
break;
- case SampleFormat::SAMPLE_FMT_INVALID:
- case SampleFormat::SAMPLE_FMT_COUNT:
+ case AudioParams::kFormatInvalid:
+ case AudioParams::kFormatCount:
qWarning() << "Invalid sample format for WAVE audio";
file_.close();
return false;
diff --git a/app/codec/waveoutput.h b/app/codec/waveoutput.h
index a42f103e0..450fe38bc 100644
--- a/app/codec/waveoutput.h
+++ b/app/codec/waveoutput.h
@@ -24,7 +24,6 @@
#include
#include
-#include "audio/sampleformat.h"
#include "render/audioparams.h"
OLIVE_NAMESPACE_ENTER
diff --git a/app/common/CMakeLists.txt b/app/common/CMakeLists.txt
index 99521453c..06963187b 100644
--- a/app/common/CMakeLists.txt
+++ b/app/common/CMakeLists.txt
@@ -16,42 +16,49 @@
set(OLIVE_SOURCES
${OLIVE_SOURCES}
- common/bezier.h
common/bezier.cpp
+ common/bezier.h
common/cancelableobject.h
common/channellayout.h
common/clamp.h
- common/commandlineparser.h
common/commandlineparser.cpp
- common/crashpadinterface.h
+ common/commandlineparser.h
common/crashpadinterface.cpp
+ common/crashpadinterface.h
common/crashpadutils.h
- common/debug.h
common/debug.cpp
+ common/debug.h
common/define.h
- common/filefunctions.h
+ common/ffmpegutils.cpp
+ common/ffmpegutils.h
common/filefunctions.cpp
- common/flipmodifiers.h
+ common/filefunctions.h
common/flipmodifiers.cpp
+ common/flipmodifiers.h
common/functiontimer.h
common/lerp.h
- common/memorypool.h
common/memorypool.cpp
- common/qtutils.h
+ common/memorypool.h
+ common/ocioutils.cpp
+ common/ocioutils.h
+ common/oiioutils.cpp
+ common/oiioutils.h
common/qtutils.cpp
+ common/qtutils.h
common/range.h
- common/ratiodialog.h
common/ratiodialog.cpp
+ common/ratiodialog.h
common/rational.h
common/rational.cpp
- common/threadedobject.h
+ common/threadsafemap.h
common/threadedobject.cpp
- common/timecodefunctions.h
+ common/threadedobject.h
common/timecodefunctions.cpp
- common/timerange.h
+ common/timecodefunctions.h
common/timerange.cpp
+ common/timerange.h
common/tohex.h
- common/xmlutils.h
common/xmlutils.cpp
+ common/xmlutils.h
PARENT_SCOPE
)
diff --git a/app/common/cancelableobject.h b/app/common/cancelableobject.h
index 19364a521..00d5254c9 100644
--- a/app/common/cancelableobject.h
+++ b/app/common/cancelableobject.h
@@ -34,14 +34,20 @@ public:
{
}
- void Cancel() {
+ void Cancel()
+ {
cancelled_ = true;
+ CancelEvent();
}
- const QAtomicInt& IsCancelled() const {
+ const QAtomicInt& IsCancelled() const
+ {
return cancelled_;
}
+protected:
+ virtual void CancelEvent(){}
+
private:
QAtomicInt cancelled_;
diff --git a/app/common/define.h b/app/common/define.h
index aa968fcf7..0a29394a0 100644
--- a/app/common/define.h
+++ b/app/common/define.h
@@ -29,10 +29,6 @@
OLIVE_NAMESPACE_ENTER
-const int kHSVChannels = 3;
-const int kRGBChannels = 3;
-const int kRGBAChannels = 4;
-
/// The minimum size an icon in ProjectExplorer can be
const int kProjectIconSizeMinimum = 16;
diff --git a/app/common/ffmpegutils.cpp b/app/common/ffmpegutils.cpp
new file mode 100644
index 000000000..9453bd851
--- /dev/null
+++ b/app/common/ffmpegutils.cpp
@@ -0,0 +1,141 @@
+/***
+
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2019 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 "common/ffmpegutils.h"
+
+OLIVE_NAMESPACE_ENTER
+
+AVPixelFormat FFmpegUtils::GetCompatiblePixelFormat(const AVPixelFormat &pix_fmt)
+{
+ AVPixelFormat possible_pix_fmts[] = {
+ AV_PIX_FMT_RGB24,
+ AV_PIX_FMT_RGBA,
+ AV_PIX_FMT_RGB48,
+ AV_PIX_FMT_RGBA64,
+ AV_PIX_FMT_NONE
+ };
+
+ return avcodec_find_best_pix_fmt_of_list(possible_pix_fmts,
+ pix_fmt,
+ 1,
+ nullptr);
+}
+
+AudioParams::Format FFmpegUtils::GetNativeSampleFormat(const AVSampleFormat &smp_fmt)
+{
+ switch (smp_fmt) {
+ case AV_SAMPLE_FMT_U8:
+ return AudioParams::kFormatUnsigned8;
+ case AV_SAMPLE_FMT_S16:
+ return AudioParams::kFormatSigned16;
+ case AV_SAMPLE_FMT_S32:
+ return AudioParams::kFormatSigned32;
+ case AV_SAMPLE_FMT_S64:
+ return AudioParams::kFormatSigned64;
+ case AV_SAMPLE_FMT_FLT:
+ return AudioParams::kFormatFloat32;
+ case AV_SAMPLE_FMT_DBL:
+ return AudioParams::kFormatFloat64;
+ case AV_SAMPLE_FMT_U8P :
+ case AV_SAMPLE_FMT_S16P:
+ case AV_SAMPLE_FMT_S32P:
+ case AV_SAMPLE_FMT_S64P:
+ case AV_SAMPLE_FMT_FLTP:
+ case AV_SAMPLE_FMT_DBLP:
+ case AV_SAMPLE_FMT_NONE:
+ case AV_SAMPLE_FMT_NB:
+ break;
+ }
+
+ return AudioParams::kFormatInvalid;
+}
+
+AVSampleFormat FFmpegUtils::GetFFmpegSampleFormat(const AudioParams::Format &smp_fmt)
+{
+ switch (smp_fmt) {
+ case AudioParams::kFormatUnsigned8:
+ return AV_SAMPLE_FMT_U8;
+ case AudioParams::kFormatSigned16:
+ return AV_SAMPLE_FMT_S16;
+ case AudioParams::kFormatSigned32:
+ return AV_SAMPLE_FMT_S32;
+ case AudioParams::kFormatSigned64:
+ return AV_SAMPLE_FMT_S64;
+ case AudioParams::kFormatFloat32:
+ return AV_SAMPLE_FMT_FLT;
+ case AudioParams::kFormatFloat64:
+ return AV_SAMPLE_FMT_DBL;
+ case AudioParams::kFormatInvalid:
+ case AudioParams::kFormatCount:
+ break;
+ }
+
+ return AV_SAMPLE_FMT_NONE;
+}
+
+AVPixelFormat FFmpegUtils::GetFFmpegPixelFormat(const VideoParams::Format &pix_fmt, int channel_layout)
+{
+ if (channel_layout == VideoParams::kRGBChannelCount) {
+ switch (pix_fmt) {
+ case VideoParams::kFormatUnsigned8:
+ return AV_PIX_FMT_RGB24;
+ case VideoParams::kFormatUnsigned16:
+ return AV_PIX_FMT_RGB48;
+ case VideoParams::kFormatFloat16:
+ case VideoParams::kFormatFloat32:
+ case VideoParams::kFormatInvalid:
+ case VideoParams::kFormatCount:
+ break;
+ }
+ } else if (channel_layout == VideoParams::kRGBAChannelCount) {
+ switch (pix_fmt) {
+ case VideoParams::kFormatUnsigned8:
+ return AV_PIX_FMT_RGBA;
+ case VideoParams::kFormatUnsigned16:
+ return AV_PIX_FMT_RGBA64;
+ case VideoParams::kFormatFloat16:
+ case VideoParams::kFormatFloat32:
+ case VideoParams::kFormatInvalid:
+ case VideoParams::kFormatCount:
+ break;
+ }
+ }
+
+ return AV_PIX_FMT_NONE;
+}
+
+VideoParams::Format FFmpegUtils::GetCompatiblePixelFormat(const VideoParams::Format &pix_fmt)
+{
+ switch (pix_fmt) {
+ case VideoParams::kFormatUnsigned8:
+ return VideoParams::kFormatUnsigned8;
+ case VideoParams::kFormatUnsigned16:
+ case VideoParams::kFormatFloat16:
+ case VideoParams::kFormatFloat32:
+ return VideoParams::kFormatUnsigned16;
+ case VideoParams::kFormatInvalid:
+ case VideoParams::kFormatCount:
+ break;
+ }
+
+ return VideoParams::kFormatInvalid;
+}
+
+OLIVE_NAMESPACE_EXIT
diff --git a/app/codec/ffmpeg/ffmpegcommon.h b/app/common/ffmpegutils.h
similarity index 77%
rename from app/codec/ffmpeg/ffmpegcommon.h
rename to app/common/ffmpegutils.h
index 106452fad..6e9fb002a 100644
--- a/app/codec/ffmpeg/ffmpegcommon.h
+++ b/app/common/ffmpegutils.h
@@ -25,12 +25,12 @@ extern "C" {
#include
}
-#include "audio/sampleformat.h"
-#include "render/pixelformat.h"
+#include "render/audioparams.h"
+#include "render/videoparams.h"
OLIVE_NAMESPACE_ENTER
-class FFmpegCommon {
+class FFmpegUtils {
public:
/**
* @brief Returns an AVPixelFormat that can be used to convert a frame to a data type Olive supports with minimal data loss
@@ -40,22 +40,22 @@ public:
/**
* @brief Returns a native pixel format that can be used to convert from a native frame to an AVFrame with minimal data loss
*/
- static PixelFormat::Format GetCompatiblePixelFormat(const PixelFormat::Format& pix_fmt);
+ static VideoParams::Format GetCompatiblePixelFormat(const VideoParams::Format& pix_fmt);
/**
* @brief Returns an FFmpeg pixel format for a given native pixel format
*/
- static AVPixelFormat GetFFmpegPixelFormat(const PixelFormat::Format& pix_fmt);
+ static AVPixelFormat GetFFmpegPixelFormat(const VideoParams::Format& pix_fmt, int channel_layout);
/**
* @brief Returns a native sample format type for a given AVSampleFormat
*/
- static SampleFormat::Format GetNativeSampleFormat(const AVSampleFormat& smp_fmt);
+ static AudioParams::Format GetNativeSampleFormat(const AVSampleFormat& smp_fmt);
/**
* @brief Returns an FFmpeg sample format type for a given native type
*/
- static AVSampleFormat GetFFmpegSampleFormat(const SampleFormat::Format &smp_fmt);
+ static AVSampleFormat GetFFmpegSampleFormat(const AudioParams::Format &smp_fmt);
};
OLIVE_NAMESPACE_EXIT
diff --git a/app/common/filefunctions.cpp b/app/common/filefunctions.cpp
index 23f3abf99..513d781ea 100644
--- a/app/common/filefunctions.cpp
+++ b/app/common/filefunctions.cpp
@@ -75,7 +75,7 @@ QString FileFunctions::GetTempFilePath()
{
QString temp_path = QDir(QDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation))
.filePath(QCoreApplication::organizationName()))
- .filePath(QCoreApplication::applicationName());
+ .filePath(QCoreApplication::applicationName());
// Ensure it exists
QDir(temp_path).mkpath(".");
@@ -187,4 +187,57 @@ QString FileFunctions::EnsureFilenameExtension(QString fn, const QString &extens
return fn;
}
+QString FileFunctions::ReadFileAsString(const QString &filename)
+{
+ QFile f(filename);
+ QString file_data;
+ if (f.open(QFile::ReadOnly | QFile::Text)) {
+ QTextStream text_stream(&f);
+ file_data = text_stream.readAll();
+ f.close();
+ }
+ return file_data;
+}
+
+QString FileFunctions::GetSafeTemporaryFilename(const QString &original)
+{
+ int counter = 0;
+
+ QFileInfo original_info(original);
+ QString basename = original_info.baseName();
+ QString complete_suffix = original_info.completeSuffix();
+
+ // If we have a complete suffix, make sure there's a period in it
+ if (!complete_suffix.isEmpty()) {
+ complete_suffix.prepend('.');
+ }
+
+ QString temp_abs_path;
+ do {
+ temp_abs_path = original_info.dir().filePath(
+ QStringLiteral("%1.tmp%2%3").arg(basename,
+ QString::number(counter),
+ complete_suffix));
+ counter++;
+ } while (QFileInfo::exists(temp_abs_path));
+
+ return temp_abs_path;
+}
+
+bool FileFunctions::RenameFileAllowOverwrite(const QString &from, const QString &to)
+{
+ if (QFileInfo::exists(to) && !QFile::remove(to)) {
+ qCritical() << "Couldn't remove existing file" << to << "for overwrite";
+ return false;
+ }
+
+ // By this point, we can assume `to` either never existed or has now been deleted
+ if (!QFile::rename(from, to)) {
+ qCritical() << "Failed to rename file" << from << "to" << to;
+ return false;
+ }
+
+ return true;
+}
+
OLIVE_NAMESPACE_EXIT
diff --git a/app/common/filefunctions.h b/app/common/filefunctions.h
index 87bcc6375..f19aac9b9 100644
--- a/app/common/filefunctions.h
+++ b/app/common/filefunctions.h
@@ -65,6 +65,25 @@ public:
*/
static QString EnsureFilenameExtension(QString fn, const QString& extension);
+ static QString ReadFileAsString(const QString& filename);
+
+ /**
+ * @brief Returns a temporary filename that can be used while writing rather than the original
+ *
+ * If overwriting a file, it's safest to write to a new file first and then only replace it at
+ * the end so that if the program crashes or the user cancels the save half way through, the
+ * original file is still intact.
+ *
+ * This function returns a slight variant of the filename provided that's guaranteed to not exist
+ * and therefore won't overwrite anything important.
+ */
+ static QString GetSafeTemporaryFilename(const QString& original);
+
+ /**
+ * @brief Renames a file from `from` to `to`, deleting `to` if such a file already exists first
+ */
+ static bool RenameFileAllowOverwrite(const QString& from, const QString& to);
+
};
diff --git a/app/audio/sampleformat.h b/app/common/ocioutils.cpp
similarity index 57%
rename from app/audio/sampleformat.h
rename to app/common/ocioutils.cpp
index 8b4dd5a9f..c5f182450 100644
--- a/app/audio/sampleformat.h
+++ b/app/common/ocioutils.cpp
@@ -18,41 +18,30 @@
***/
-#ifndef SAMPLEFORMAT_H
-#define SAMPLEFORMAT_H
-
-#include
-
-#include "common/define.h"
-#include "render/rendermodes.h"
+#include "ocioutils.h"
OLIVE_NAMESPACE_ENTER
-class SampleFormat : public QObject
+OCIO::BitDepth OCIOUtils::GetOCIOBitDepthFromPixelFormat(VideoParams::Format format)
{
- Q_OBJECT
-public:
- SampleFormat() = default;
+ switch (format) {
+ case VideoParams::kFormatUnsigned8:
+ return OCIO::BIT_DEPTH_UINT8;
+ case VideoParams::kFormatUnsigned16:
+ return OCIO::BIT_DEPTH_UINT16;
+ break;
+ case VideoParams::kFormatFloat16:
+ return OCIO::BIT_DEPTH_F16;
+ break;
+ case VideoParams::kFormatFloat32:
+ return OCIO::BIT_DEPTH_F32;
+ break;
+ case VideoParams::kFormatInvalid:
+ case VideoParams::kFormatCount:
+ break;
+ }
- enum Format {
- SAMPLE_FMT_INVALID = -1,
-
- SAMPLE_FMT_U8,
- SAMPLE_FMT_S16,
- SAMPLE_FMT_S32,
- SAMPLE_FMT_S64,
- SAMPLE_FMT_FLT,
- SAMPLE_FMT_DBL,
-
- SAMPLE_FMT_COUNT
- };
-
- static const Format kInternalFormat;
-
- static QString GetSampleFormatName(const Format& f);
-
-};
+ return OCIO::BIT_DEPTH_UNKNOWN;
+}
OLIVE_NAMESPACE_EXIT
-
-#endif // SAMPLEFORMAT_H
diff --git a/app/render/backend/opengl/openglbackend.cpp b/app/common/ocioutils.h
similarity index 73%
rename from app/render/backend/opengl/openglbackend.cpp
rename to app/common/ocioutils.h
index 60e81f914..edbaa9fef 100644
--- a/app/render/backend/opengl/openglbackend.cpp
+++ b/app/common/ocioutils.h
@@ -18,26 +18,22 @@
***/
-#include "openglbackend.h"
+#ifndef OCIOUTILS_H
+#define OCIOUTILS_H
-#include "openglworker.h"
+#include
+namespace OCIO = OpenColorIO_v2_0dev;
+
+#include "render/videoparams.h"
OLIVE_NAMESPACE_ENTER
-OpenGLBackend::OpenGLBackend(QObject* parent) :
- RenderBackend(parent)
+class OCIOUtils
{
-
-}
-
-OpenGLBackend::~OpenGLBackend()
-{
- Close();
-}
-
-RenderWorker *OpenGLBackend::CreateNewWorker()
-{
- return new OpenGLWorker(this);
-}
+public:
+ static OCIO::BitDepth GetOCIOBitDepthFromPixelFormat(VideoParams::Format format);
+};
OLIVE_NAMESPACE_EXIT
+
+#endif // OCIOUTILS_H
diff --git a/app/common/oiioutils.cpp b/app/common/oiioutils.cpp
new file mode 100644
index 000000000..8381ba904
--- /dev/null
+++ b/app/common/oiioutils.cpp
@@ -0,0 +1,120 @@
+/***
+
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2019 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 "oiioutils.h"
+
+OLIVE_NAMESPACE_ENTER
+
+void OIIOUtils::FrameToBuffer(const Frame* frame, OIIO::ImageBuf *buf)
+{
+#if OIIO_VERSION < 20112
+ //
+ // Workaround for OIIO bug that ignores destination stride in versions OLDER than 2.1.12
+ //
+ // See more: https://github.com/OpenImageIO/oiio/pull/2487
+ //
+ int width_in_bytes = frame->width() * PixelFormat::BytesPerPixel(frame->format());
+
+ for (int i=0;ispec().height;i++) {
+ memcpy(
+#if OIIO_VERSION < 10903
+ reinterpret_cast(buf->localpixels()) + i * width_in_bytes,
+#else
+ reinterpret_cast(buf->localpixels()) + i * buf->scanline_stride(),
+#endif
+ frame->data() + i * frame->linesize_bytes(),
+ width_in_bytes);
+ }
+#else
+ buf->set_pixels(OIIO::ROI(),
+ buf->spec().format,
+ frame->const_data(),
+ OIIO::AutoStride,
+ frame->linesize_bytes());
+#endif
+}
+
+void OIIOUtils::BufferToFrame(OIIO::ImageBuf *buf, Frame* frame)
+{
+#if OIIO_VERSION < 20112
+ //
+ // Workaround for OIIO bug that ignores destination stride in versions OLDER than 2.1.12
+ //
+ // See more: https://github.com/OpenImageIO/oiio/pull/2487
+ //
+ int width_in_bytes = frame->width() * PixelFormat::BytesPerPixel(frame->format());
+
+ for (int i=0;ispec().height;i++) {
+ memcpy(frame->data() + i * frame->linesize_bytes(),
+#if OIIO_VERSION < 10903
+ reinterpret_cast(buf->localpixels()) + i * width_in_bytes,
+#else
+ reinterpret_cast(buf->localpixels()) + i * buf->scanline_stride(),
+#endif
+ width_in_bytes);
+ }
+#else
+ buf->get_pixels(OIIO::ROI(),
+ buf->spec().format,
+ frame->data(),
+ OIIO::AutoStride,
+ frame->linesize_bytes());
+#endif
+}
+
+rational OIIOUtils::GetPixelAspectRatioFromOIIO(const OIIO::ImageSpec &spec)
+{
+ return rational::fromDouble(spec.get_float_attribute("PixelAspectRatio", 1));
+}
+
+VideoParams::Format OIIOUtils::GetFormatFromOIIOBasetype(OIIO::TypeDesc::BASETYPE type)
+{
+ switch (type) {
+ case OIIO::TypeDesc::UNKNOWN:
+ case OIIO::TypeDesc::NONE:
+ break;
+
+ case OIIO::TypeDesc::INT8:
+ case OIIO::TypeDesc::INT16:
+ case OIIO::TypeDesc::INT32:
+ case OIIO::TypeDesc::UINT32:
+ case OIIO::TypeDesc::INT64:
+ case OIIO::TypeDesc::UINT64:
+ case OIIO::TypeDesc::STRING:
+ case OIIO::TypeDesc::PTR:
+ case OIIO::TypeDesc::LASTBASE:
+ case OIIO::TypeDesc::DOUBLE:
+ qDebug() << "Tried to use unknown OIIO base type";
+ break;
+
+ case OIIO::TypeDesc::UINT8:
+ return VideoParams::kFormatUnsigned8;
+ case OIIO::TypeDesc::UINT16:
+ return VideoParams::kFormatUnsigned16;
+ case OIIO::TypeDesc::HALF:
+ return VideoParams::kFormatFloat16;
+ case OIIO::TypeDesc::FLOAT:
+ return VideoParams::kFormatFloat32;
+ }
+
+ return VideoParams::kFormatInvalid;
+}
+
+OLIVE_NAMESPACE_EXIT
diff --git a/app/common/oiioutils.h b/app/common/oiioutils.h
new file mode 100644
index 000000000..a9dca5baa
--- /dev/null
+++ b/app/common/oiioutils.h
@@ -0,0 +1,65 @@
+/***
+
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2019 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 OIIOUTILS_H
+#define OIIOUTILS_H
+
+#include
+#include
+
+#include "codec/frame.h"
+#include "render/videoparams.h"
+
+OLIVE_NAMESPACE_ENTER
+
+class OIIOUtils {
+public:
+ static OIIO::TypeDesc::BASETYPE GetOIIOBaseTypeFromFormat(VideoParams::Format format)
+ {
+ switch (format) {
+ case VideoParams::kFormatUnsigned8:
+ return OIIO::TypeDesc::UINT8;
+ case VideoParams::kFormatUnsigned16:
+ return OIIO::TypeDesc::UINT16;
+ case VideoParams::kFormatFloat16:
+ return OIIO::TypeDesc::HALF;
+ case VideoParams::kFormatFloat32:
+ return OIIO::TypeDesc::FLOAT;
+ case VideoParams::kFormatInvalid:
+ case VideoParams::kFormatCount:
+ break;
+ }
+
+ return OIIO::TypeDesc::UNKNOWN;
+ }
+
+ static void FrameToBuffer(const Frame *frame, OIIO::ImageBuf* buf);
+
+ static void BufferToFrame(OIIO::ImageBuf* buf, Frame* frame);
+
+ static VideoParams::Format GetFormatFromOIIOBasetype(OIIO::TypeDesc::BASETYPE type);
+
+ static rational GetPixelAspectRatioFromOIIO(const OIIO::ImageSpec& spec);
+
+};
+
+OLIVE_NAMESPACE_EXIT
+
+#endif // OIIOUTILS_H
diff --git a/app/common/threadsafemap.h b/app/common/threadsafemap.h
new file mode 100644
index 000000000..0d4a6bc7e
--- /dev/null
+++ b/app/common/threadsafemap.h
@@ -0,0 +1,27 @@
+#ifndef THREADSAFEMAP_H
+#define THREADSAFEMAP_H
+
+#include
+#include
+
+template
+class ThreadSafeMap
+{
+public:
+ ThreadSafeMap() = default;
+
+ void insert(K key, V value)
+ {
+ mutex_.lock();
+ map_.insert(key, value);
+ mutex_.unlock();
+ }
+
+private:
+ QMutex mutex_;
+
+ QMap map_;
+
+};
+
+#endif // THREADSAFEMAP_H
diff --git a/app/common/timerange.cpp b/app/common/timerange.cpp
index d8b2a5b6b..639b5c030 100644
--- a/app/common/timerange.cpp
+++ b/app/common/timerange.cpp
@@ -20,6 +20,7 @@
#include "timerange.h"
+#include
#include
OLIVE_NAMESPACE_ENTER
@@ -77,11 +78,11 @@ bool TimeRange::operator!=(const TimeRange &r) const
bool TimeRange::OverlapsWith(const TimeRange &a, bool in_inclusive, bool out_inclusive) const
{
- bool overlaps_in = (in_inclusive) ? (a.out() < in()) : (a.out() <= in());
+ bool doesnt_overlap_in = (in_inclusive) ? (a.out() < in()) : (a.out() <= in());
- bool overlaps_out = (out_inclusive) ? (a.in() > out()) : (a.in() >= out());
+ bool doesnt_overlap_out = (out_inclusive) ? (a.in() > out()) : (a.in() >= out());
- return !(overlaps_in || overlaps_out);
+ return !doesnt_overlap_in && !doesnt_overlap_out;
}
TimeRange TimeRange::Combined(const TimeRange &a) const
@@ -148,6 +149,21 @@ const TimeRange &TimeRange::operator-=(const rational &rhs)
return *this;
}
+std::list TimeRange::Split(const int &chunk_size) const
+{
+ std::list split_ranges;
+
+ int start_time = qFloor(this->in().toDouble() / static_cast(chunk_size)) * chunk_size;
+ int end_time = qCeil(this->out().toDouble() / static_cast(chunk_size)) * chunk_size;
+
+ for (int i=start_time; iin(), rational(i)),
+ qMin(this->out(), rational(i + chunk_size))));
+ }
+
+ return split_ranges;
+}
+
void TimeRange::normalize()
{
// If `out` is earlier than `in`, swap them
@@ -160,43 +176,45 @@ void TimeRange::normalize()
length_ = out_ - in_;
}
-void TimeRangeList::InsertTimeRange(TimeRange range_to_add)
+void TimeRangeList::insert(TimeRange range_to_add)
{
// See if list contains this range
- if (ContainsTimeRange(range_to_add)) {
+ if (contains(range_to_add)) {
return;
}
// Does not contain range, so we'll almost certainly be adding it in some way
for (int i=0;isize();
for (int i=0;iremoveAt(i);
+ array_.removeAt(i);
i--;
sz--;
} else if (compare.Contains(remove, false, false)) {
// The remove range is within this element, only choice is to split the element into two
- this->append(TimeRange(remove.out(), compare.out()));
+ TimeRange new_range(remove.out(), compare.out());
compare.set_out(remove.in());
+ insert(new_range);
+ break;
} else if (compare.in() < remove.in() && compare.out() > remove.in()) {
// This element's out point overlaps the range's in, we'll trim it
compare.set_out(remove.in());
@@ -207,10 +225,10 @@ void TimeRangeList::RemoveTimeRange(const TimeRange &remove)
}
}
-bool TimeRangeList::ContainsTimeRange(const TimeRange &range, bool in_inclusive, bool out_inclusive) const
+bool TimeRangeList::contains(const TimeRange &range, bool in_inclusive, bool out_inclusive) const
{
for (int i=0;i= range.out()) {
// No intersect
@@ -233,22 +284,13 @@ TimeRangeList TimeRangeList::Intersects(const TimeRange &range) const
TimeRange cropped(qMax(range.in(), compare.in()),
qMin(range.out(), compare.out()));
- intersect_list.append(cropped);
+ intersect_list.insert(cropped);
}
}
return intersect_list;
}
-void TimeRangeList::PrintTimeList()
-{
- qDebug() << "TimeRangeList now contains:";
-
- for (int i=0;i Split(const int &chunk_size) const;
+
private:
void normalize();
@@ -65,25 +67,73 @@ private:
};
-class TimeRangeList : public QList {
+class TimeRangeList {
public:
TimeRangeList() = default;
TimeRangeList(std::initializer_list r) :
- QList(r)
+ array_(r)
{
}
- void InsertTimeRange(TimeRange range_to_add);
+ void insert(TimeRange range_to_add);
- void RemoveTimeRange(const TimeRange& remove);
+ void remove(const TimeRange& remove);
- bool ContainsTimeRange(const TimeRange& range, bool in_inclusive = true, bool out_inclusive = true) const;
+ bool contains(const TimeRange& range, bool in_inclusive = true, bool out_inclusive = true) const;
+
+ bool isEmpty() const
+ {
+ return array_.isEmpty();
+ }
+
+ void clear()
+ {
+ array_.clear();
+ }
+
+ int size() const
+ {
+ return array_.size();
+ }
+
+ void shift(const rational& diff);
+
+ void trim_in(const rational& diff);
+
+ void trim_out(const rational& diff);
TimeRangeList Intersects(const TimeRange& range) const;
+ using const_iterator = QVector::const_iterator;
+
+ const_iterator begin() const
+ {
+ return array_.constBegin();
+ }
+
+ const_iterator end() const
+ {
+ return array_.constEnd();
+ }
+
+ const TimeRange& first() const
+ {
+ return array_.first();
+ }
+
+ const TimeRange& last() const
+ {
+ return array_.last();
+ }
+
+ const QVector& internal_array() const
+ {
+ return array_;
+ }
+
private:
- void PrintTimeList();
+ QVector array_;
};
@@ -92,6 +142,7 @@ uint qHash(const TimeRange& r, uint seed);
OLIVE_NAMESPACE_EXIT
QDebug operator<<(QDebug debug, const OLIVE_NAMESPACE::TimeRange& r);
+QDebug operator<<(QDebug debug, const OLIVE_NAMESPACE::TimeRangeList& r);
Q_DECLARE_METATYPE(OLIVE_NAMESPACE::TimeRange)
diff --git a/app/config/config.cpp b/app/config/config.cpp
index f2db463d7..2b317a51c 100644
--- a/app/config/config.cpp
+++ b/app/config/config.cpp
@@ -89,6 +89,7 @@ void Config::SetDefaults()
SetEntryInternal(QStringLiteral("RectifiedWaveforms"), NodeParam::kBoolean, false);
SetEntryInternal(QStringLiteral("DropWithoutSequenceBehavior"), NodeParam::kInt, ImportTool::kDWSAsk);
SetEntryInternal(QStringLiteral("Loop"), NodeParam::kBoolean, false);
+ SetEntryInternal(QStringLiteral("SplitClipsCopyNodes"), NodeParam::kBoolean, true);
SetEntryInternal(QStringLiteral("AutoCacheInterval"), NodeParam::kInt, 250);
@@ -116,13 +117,10 @@ void Config::SetDefaults()
SetEntryInternal(QStringLiteral("DefaultSequenceInterlacing"), NodeParam::kInt, VideoParams::kInterlaceNone);
SetEntryInternal(QStringLiteral("DefaultSequenceAudioFrequency"), NodeParam::kInt, 48000);
SetEntryInternal(QStringLiteral("DefaultSequenceAudioLayout"), NodeParam::kInt, QVariant::fromValue(static_cast(AV_CH_LAYOUT_STEREO)));
- SetEntryInternal(QStringLiteral("DefaultSequencePreviewFormat"), NodeParam::kInt, PixelFormat::PIX_FMT_RGBA16F);
// Online/offline settings
- SetEntryInternal(QStringLiteral("OnlinePixelFormat"), NodeParam::kInt, PixelFormat::PIX_FMT_RGBA32F);
- SetEntryInternal(QStringLiteral("OfflinePixelFormat"), NodeParam::kInt, PixelFormat::PIX_FMT_RGBA16F);
- SetEntryInternal(QStringLiteral("OnlineOCIOMethod"), NodeParam::kInt, ColorManager::kOCIOAccurate);
- SetEntryInternal(QStringLiteral("OfflineOCIOMethod"), NodeParam::kInt, ColorManager::kOCIOFast);
+ SetEntryInternal(QStringLiteral("OnlinePixelFormat"), NodeParam::kInt, VideoParams::kFormatFloat32);
+ SetEntryInternal(QStringLiteral("OfflinePixelFormat"), NodeParam::kInt, VideoParams::kFormatFloat16);
}
void Config::Load()
@@ -206,7 +204,10 @@ void Config::Load()
void Config::Save()
{
- QFile config_file(GetConfigFilePath());
+ QString real_filename = GetConfigFilePath();
+ QString temp_filename = FileFunctions::GetSafeTemporaryFilename(real_filename);
+
+ QFile config_file(temp_filename);
if (!config_file.open(QFile::WriteOnly)) {
QMessageBox::critical(Core::instance()->main_window(),
@@ -233,10 +234,10 @@ void Config::Save()
QString value = NodeInput::ValueToString(iterator.value().type, iterator.value().data, false);
- writer.writeTextElement(iterator.key(), value);
-
if (iterator.value().type == NodeParam::kNone) {
- qWarning() << "Config key" << iterator.key() << "had null type";
+ qWarning() << "Config key" << iterator.key() << "had null type and was discarded";
+ } else {
+ writer.writeTextElement(iterator.key(), value);
}
}
@@ -245,6 +246,11 @@ void Config::Save()
writer.writeEndDocument();
config_file.close();
+
+ if (!FileFunctions::RenameFileAllowOverwrite(temp_filename, real_filename)) {
+ qWarning() << QStringLiteral("Failed to overwrite \"%1\". Config has been saved as \"%2\" instead.")
+ .arg(real_filename, temp_filename);
+ }
}
QVariant Config::operator[](const QString &key) const
diff --git a/app/core.cpp b/app/core.cpp
index c070bac7e..60be123ed 100644
--- a/app/core.cpp
+++ b/app/core.cpp
@@ -49,11 +49,9 @@
#include "panel/panelmanager.h"
#include "panel/project/project.h"
#include "panel/viewer/viewer.h"
-#include "render/backend/opengl/opengltexturecache.h"
#include "render/colormanager.h"
#include "render/diskmanager.h"
-#include "render/pixelformat.h"
-#include "render/shaderinfo.h"
+#include "render/rendermanager.h"
#ifdef USE_OTIO
#include "task/project/loadotio/loadotio.h"
#include "task/project/saveotio/saveotio.h"
@@ -95,8 +93,6 @@ Core *Core::instance()
void Core::DeclareTypesForQt()
{
qRegisterMetaType();
- qRegisterMetaType();
- qRegisterMetaType();
qRegisterMetaType();
qRegisterMetaType();
qRegisterMetaType();
@@ -135,8 +131,8 @@ void Core::Start()
// Initialize task manager
TaskManager::CreateInstance();
- // Initialize OpenGL service
- OpenGLProxy::CreateInstance();
+ // Initialize RenderManager
+ RenderManager::CreateInstance();
//
// Start application
@@ -172,15 +168,13 @@ void Core::Stop()
if (recent_projects_file.open(QFile::WriteOnly | QFile::Text)) {
QTextStream ts(&recent_projects_file);
- foreach (const QString& s, recent_projects_) {
- ts << s << "\n";
- }
+ ts << recent_projects_.join('\n');
recent_projects_file.close();
}
}
- OpenGLProxy::DestroyInstance();
+ RenderManager::DestroyInstance();
MenuShared::DestroyInstance();
@@ -192,11 +186,10 @@ void Core::Stop()
DiskManager::DestroyInstance();
- PixelFormat::DestroyInstance();
-
NodeFactory::Destroy();
delete main_window_;
+ main_window_ = nullptr;
}
MainWindow *Core::main_window()
@@ -259,6 +252,7 @@ void Core::SetSelectedTransitionObject(const QString &obj)
void Core::ClearOpenRecentList()
{
recent_projects_.clear();
+ emit OpenRecentListChanged();
}
void Core::CreateNewProject()
@@ -648,9 +642,6 @@ void Core::StartGUI(bool full_screen)
// Initialize disk service
DiskManager::CreateInstance();
- // Initialize pixel service
- PixelFormat::CreateInstance();
-
// Connect the PanelFocusManager to the application's focus change signal
connect(qApp,
&QApplication::focusChanged,
@@ -686,12 +677,15 @@ void Core::StartGUI(bool full_screen)
if (recent_projects_file.open(QFile::ReadOnly | QFile::Text)) {
QTextStream ts(&recent_projects_file);
- while (!ts.atEnd()) {
- recent_projects_.append(ts.readLine());
+ QString s;
+ while (!(s = ts.readLine()).isEmpty()) {
+ recent_projects_.append(s);
}
recent_projects_file.close();
}
+
+ emit OpenRecentListChanged();
}
}
@@ -961,6 +955,8 @@ void Core::PushRecentlyOpenedProject(const QString& s)
} else {
recent_projects_.prepend(s);
}
+
+ emit OpenRecentListChanged();
}
void Core::OpenProjectInternal(const QString &filename)
@@ -1038,7 +1034,7 @@ void Core::SetPreferenceForRenderMode(RenderMode::Mode mode, const QString &pref
Config::Current()[GetRenderModePreferencePrefix(mode, preference)] = value;
}
-void Core::LabelNodes(const QList &nodes) const
+void Core::LabelNodes(const QVector &nodes) const
{
if (nodes.isEmpty()) {
return;
@@ -1097,6 +1093,8 @@ void Core::OpenProjectFromRecentList(int index)
tr("The project \"%1\" doesn't exist. Would you like to remove this file from the recent list?").arg(open_fn),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
recent_projects_.removeAt(index);
+
+ emit OpenRecentListChanged();
}
}
diff --git a/app/core.h b/app/core.h
index 8e3b45db1..62257a36a 100644
--- a/app/core.h
+++ b/app/core.h
@@ -233,7 +233,7 @@ public:
/**
* @brief Show a dialog to the user to rename a set of nodes
*/
- void LabelNodes(const QList& nodes) const;
+ void LabelNodes(const QVector &nodes) const;
/**
* @brief Create a new sequence named appropriately for the active project
@@ -415,6 +415,11 @@ signals:
*/
void TimecodeDisplayChanged(Timecode::Display d);
+ /**
+ * @brief Signal emitted when a change is made to the open recent list
+ */
+ void OpenRecentListChanged();
+
private:
/**
* @brief Get the file filter than can be used with QFileDialog to open and save compatible projects
diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp
index 2d3250be9..ccc3df207 100644
--- a/app/dialog/export/export.cpp
+++ b/app/dialog/export/export.cpp
@@ -34,7 +34,6 @@
#include "dialog/task/task.h"
#include "project/item/sequence/sequence.h"
#include "project/project.h"
-#include "render/pixelformat.h"
#include "ui/icons/icons.h"
OLIVE_NAMESPACE_ENTER
@@ -179,6 +178,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
video_tab_->height_slider()->SetDefaultValue(viewer_node_->video_params().height());
video_tab_->frame_rate_combobox()->SetFrameRate(viewer_node_->video_params().time_base().flipped());
video_tab_->pixel_aspect_combobox()->SetPixelAspectRatio(viewer_node_->video_params().pixel_aspect_ratio());
+ video_tab_->pixel_format_field()->SetPixelFormat(static_cast(Config::Current()["OnlinePixelFormat"].toInt()));
video_tab_->interlaced_combobox()->SetInterlaceMode(viewer_node_->video_params().interlacing());
audio_tab_->sample_rate_combobox()->SetSampleRate(viewer_node_->audio_params().sample_rate());
audio_tab_->channel_layout_combobox()->SetChannelLayout(viewer_node_->audio_params().channel_layout());
@@ -285,25 +285,36 @@ void ExportDialog::StartExport()
}
// Validate video resolution
- if (video_enabled_->isChecked()) {
- if (video_tab_->width_slider()->GetValue() % 2 != 0
- || video_tab_->height_slider()->GetValue() % 2 != 0) {
- QMessageBox b(this);
- b.setIcon(QMessageBox::Critical);
- b.setWindowModality(Qt::WindowModal);
- b.setWindowTitle(tr("Invalid parameters"));
- b.setText(tr("Width and height must be multiples of 2."));
- b.exec();
- return;
- }
+ if (video_enabled_->isChecked()
+ && video_tab_->GetSelectedCodec() == ExportCodec::kCodecH264
+ && (video_tab_->width_slider()->GetValue()%2 != 0 || video_tab_->height_slider()->GetValue()%2 != 0)) {
+ QMessageBox b(this);
+ b.setIcon(QMessageBox::Critical);
+ b.setWindowModality(Qt::WindowModal);
+ b.setWindowTitle(tr("Invalid Parameters"));
+ b.setText(tr("Width and height must be multiples of 2."));
+ b.exec();
+ return;
}
ExportTask* task = new ExportTask(viewer_node_, color_manager_, GenerateParams());
TaskDialog* td = new TaskDialog(task, tr("Export"), this);
- connect(td, &TaskDialog::TaskSucceeded, this, &QDialog::accept);
+ connect(td, &TaskDialog::TaskSucceeded, this, &ExportDialog::ExportFinished);
td->open();
}
+void ExportDialog::ExportFinished()
+{
+ TaskDialog* td = static_cast(sender());
+
+ if (td->GetTask()->IsCancelled()) {
+ // If this task was cancelled, we stay open so the user can potentially queue another export
+ } else {
+ // Accept this dialog and close
+ this->accept();
+ }
+}
+
void ExportDialog::closeEvent(QCloseEvent *e)
{
preview_viewer_->ConnectViewerNode(nullptr);
@@ -373,7 +384,7 @@ void ExportDialog::ResolutionChanged()
new_width *= video_aspect_ratio_;
// Align to even number and set
- video_tab_->width_slider()->SetValue(AlignEvenNumber(new_width));
+ video_tab_->width_slider()->SetValue(new_width);
} else {
@@ -384,7 +395,7 @@ void ExportDialog::ResolutionChanged()
new_height /= video_aspect_ratio_;
// Align to even number and set
- video_tab_->height_slider()->SetValue(AlignEvenNumber(new_height));
+ video_tab_->height_slider()->SetValue(new_height);
}
}
@@ -414,24 +425,20 @@ void ExportDialog::SetDefaultFilename()
filename_edit_->setText(file_location);
}
-int ExportDialog::AlignEvenNumber(double d)
-{
- return qCeil(d * 0.5) * 2;
-}
-
ExportParams ExportDialog::GenerateParams() const
{
VideoParams video_render_params(static_cast(video_tab_->width_slider()->GetValue()),
static_cast(video_tab_->height_slider()->GetValue()),
video_tab_->frame_rate_combobox()->GetFrameRate().flipped(),
- PixelFormat::instance()->GetConfiguredFormatForMode(RenderMode::kOnline),
+ video_tab_->pixel_format_field()->GetPixelFormat(),
+ VideoParams::kInternalChannelCount,
video_tab_->pixel_aspect_combobox()->GetPixelAspectRatio(),
video_tab_->interlaced_combobox()->GetInterlaceMode(),
1);
AudioParams audio_render_params(audio_tab_->sample_rate_combobox()->currentData().toInt(),
audio_tab_->channel_layout_combobox()->GetChannelLayout(),
- SampleFormat::kInternalFormat);
+ AudioParams::kInternalFormat);
ExportParams params;
params.SetFilename(filename_edit_->text());
diff --git a/app/dialog/export/export.h b/app/dialog/export/export.h
index 8313e59cf..d92eaba3e 100644
--- a/app/dialog/export/export.h
+++ b/app/dialog/export/export.h
@@ -49,8 +49,6 @@ private:
void LoadPresets();
void SetDefaultFilename();
- static int AlignEvenNumber(double d);
-
ExportParams GenerateParams() const;
ViewerOutput* viewer_node_;
@@ -85,6 +83,8 @@ private slots:
void StartExport();
+ void ExportFinished();
+
};
OLIVE_NAMESPACE_EXIT
diff --git a/app/dialog/export/exportvideotab.cpp b/app/dialog/export/exportvideotab.cpp
index 9d62c360f..617f67269 100644
--- a/app/dialog/export/exportvideotab.cpp
+++ b/app/dialog/export/exportvideotab.cpp
@@ -115,6 +115,13 @@ QWidget* ExportVideoTab::SetupResolutionSection()
interlaced_combobox_ = new InterlacedComboBox();
layout->addWidget(interlaced_combobox_, row, 1);
+ row++;
+
+ layout->addWidget(new QLabel(tr("Quality:")), row, 0);
+
+ pixel_format_field_ = new PixelFormatComboBox(true);
+ layout->addWidget(pixel_format_field_, row, 1);
+
return resolution_group;
}
diff --git a/app/dialog/export/exportvideotab.h b/app/dialog/export/exportvideotab.h
index fe35d884b..ffbac3e19 100644
--- a/app/dialog/export/exportvideotab.h
+++ b/app/dialog/export/exportvideotab.h
@@ -111,6 +111,11 @@ public:
return pixel_aspect_combobox_;
}
+ PixelFormatComboBox* pixel_format_field() const
+ {
+ return pixel_format_field_;
+ }
+
const int& threads() const
{
return threads_;
@@ -149,6 +154,7 @@ private:
InterlacedComboBox* interlaced_combobox_;
PixelAspectRatioComboBox* pixel_aspect_combobox_;
+ PixelFormatComboBox* pixel_format_field_;
int threads_;
diff --git a/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp b/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp
index 0aaca3a43..f6ebfcfcc 100644
--- a/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp
+++ b/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp
@@ -25,9 +25,8 @@
#include
#include
#include
-#include
-namespace OCIO = OCIO_NAMESPACE::v1;
+#include "common/ocioutils.h"
#include "core.h"
#include "project/item/footage/footage.h"
#include "project/project.h"
@@ -78,11 +77,13 @@ VideoStreamProperties::VideoStreamProperties(VideoStreamPtr stream) :
video_layout->addWidget(video_color_space_, row, 1);
- row++;
+ if (stream->channel_count() == VideoParams::kRGBAChannelCount) {
+ row++;
- video_premultiply_alpha_ = new QCheckBox(tr("Premultiplied Alpha"));
- video_premultiply_alpha_->setChecked(stream_->premultiplied_alpha());
- video_layout->addWidget(video_premultiply_alpha_, row, 0, 1, 2);
+ video_premultiply_alpha_ = new QCheckBox(tr("Premultiplied Alpha"));
+ video_premultiply_alpha_->setChecked(stream_->premultiplied_alpha());
+ video_layout->addWidget(video_premultiply_alpha_, row, 0, 1, 2);
+ }
row++;
diff --git a/app/dialog/preferences/tabs/preferencesbehaviortab.cpp b/app/dialog/preferences/tabs/preferencesbehaviortab.cpp
index 6713fe81b..3feb53035 100644
--- a/app/dialog/preferences/tabs/preferencesbehaviortab.cpp
+++ b/app/dialog/preferences/tabs/preferencesbehaviortab.cpp
@@ -99,6 +99,11 @@ PreferencesBehaviorTab::PreferencesBehaviorTab()
AddItem(tr("Auto-Scale By Default"),
QStringLiteral("AutoscaleByDefault"),
node_group);
+ AddItem(tr("Splitting Clips Copies Dependencies"),
+ QStringLiteral("SplitClipsCopyNodes"),
+ tr("Multiple clips can share the same nodes. Disable this to automatically share node "
+ "dependencies among clips when copying or splitting them."),
+ node_group);
}
void PreferencesBehaviorTab::Accept()
diff --git a/app/dialog/projectproperties/projectproperties.cpp b/app/dialog/projectproperties/projectproperties.cpp
index 5a8713f5c..509a47e61 100644
--- a/app/dialog/projectproperties/projectproperties.cpp
+++ b/app/dialog/projectproperties/projectproperties.cpp
@@ -27,10 +27,9 @@
#include
#include
#include
-#include
-namespace OCIO = OCIO_NAMESPACE::v1;
#include "common/filefunctions.h"
+#include "common/ocioutils.h"
#include "config/config.h"
#include "core.h"
#include "render/colormanager.h"
diff --git a/app/dialog/sequence/sequence.cpp b/app/dialog/sequence/sequence.cpp
index 3006c2b73..0df85621e 100644
--- a/app/dialog/sequence/sequence.cpp
+++ b/app/dialog/sequence/sequence.cpp
@@ -107,13 +107,14 @@ void SequenceDialog::accept()
parameter_tab_->GetSelectedVideoHeight(),
parameter_tab_->GetSelectedVideoFrameRate().flipped(),
parameter_tab_->GetSelectedPreviewFormat(),
+ VideoParams::kInternalChannelCount,
parameter_tab_->GetSelectedVideoPixelAspect(),
parameter_tab_->GetSelectedVideoInterlacingMode(),
parameter_tab_->GetSelectedPreviewResolution());
AudioParams audio_params = AudioParams(parameter_tab_->GetSelectedAudioSampleRate(),
parameter_tab_->GetSelectedAudioChannelLayout(),
- SampleFormat::kInternalFormat);
+ AudioParams::kInternalFormat);
if (make_undoable_) {
diff --git a/app/dialog/sequence/sequencedialogparametertab.cpp b/app/dialog/sequence/sequencedialogparametertab.cpp
index 98689b16c..326df8b11 100644
--- a/app/dialog/sequence/sequencedialogparametertab.cpp
+++ b/app/dialog/sequence/sequencedialogparametertab.cpp
@@ -74,8 +74,8 @@ SequenceDialogParameterTab::SequenceDialogParameterTab(Sequence* sequence, QWidg
preview_resolution_label_ = new QLabel();
preview_layout->addWidget(preview_resolution_label_, row, 2);
row++;
- preview_layout->addWidget(new QLabel(tr("Format:")), row, 0);
- preview_format_field_ = new PixelFormatComboBox(true, true);
+ preview_layout->addWidget(new QLabel(tr("Quality:")), row, 0);
+ preview_format_field_ = new PixelFormatComboBox(true);
preview_layout->addWidget(preview_format_field_, row, 1, 1, 2);
layout->addWidget(preview_group);
@@ -133,7 +133,8 @@ void SequenceDialogParameterTab::UpdatePreviewResolutionLabel()
{
VideoParams test_param(video_width_field_->GetValue(),
video_height_field_->GetValue(),
- PixelFormat::PIX_FMT_INVALID,
+ VideoParams::kFormatInvalid,
+ VideoParams::kInternalChannelCount,
rational(1),
VideoParams::kInterlaceNone,
preview_resolution_field_->currentData().toInt());
diff --git a/app/dialog/sequence/sequencedialogparametertab.h b/app/dialog/sequence/sequencedialogparametertab.h
index 794abe505..d2dabdea1 100644
--- a/app/dialog/sequence/sequencedialogparametertab.h
+++ b/app/dialog/sequence/sequencedialogparametertab.h
@@ -58,7 +58,7 @@ public:
return preview_resolution_field_->GetDivider();
}
- PixelFormat::Format GetSelectedPreviewFormat() const
+ VideoParams::Format GetSelectedPreviewFormat() const
{
return preview_format_field_->GetPixelFormat();
}
diff --git a/app/dialog/sequence/sequencedialogpresettab.cpp b/app/dialog/sequence/sequencedialogpresettab.cpp
index 18c9419db..1f25a29fb 100644
--- a/app/dialog/sequence/sequencedialogpresettab.cpp
+++ b/app/dialog/sequence/sequencedialogpresettab.cpp
@@ -30,6 +30,7 @@
#include
#include "common/filefunctions.h"
+#include "config/config.h"
#include "node/input.h"
#include "render/videoparams.h"
#include "ui/icons/icons.h"
@@ -41,8 +42,6 @@ const int kDataIsPreset = Qt::UserRole;
const int kDataPresetIsCustomRole = Qt::UserRole + 1;
const int kDataPresetDataRole = Qt::UserRole + 2;
-const PixelFormat::Format kDefaultPreviewFormat = PixelFormat::PIX_FMT_RGBA16F;
-
SequenceDialogPresetTab::SequenceDialogPresetTab(QWidget* parent) :
QWidget(parent),
PresetManager(this, QStringLiteral("sequencepresets"))
@@ -100,6 +99,7 @@ QTreeWidgetItem* SequenceDialogPresetTab::CreateFolder(const QString &name)
QTreeWidgetItem *SequenceDialogPresetTab::CreateHDPresetFolder(const QString &name, int width, int height, int divider)
{
+ VideoParams::Format default_format = static_cast(Config::Current()["OfflinePixelFormat"].toInt());
QTreeWidgetItem* parent = CreateFolder(name);
AddStandardItem(parent, SequencePreset::Create(tr("%1 23.976 FPS").arg(name),
width,
@@ -110,7 +110,7 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateHDPresetFolder(const QString &na
48000,
AV_CH_LAYOUT_STEREO,
divider,
- kDefaultPreviewFormat));
+ default_format));
AddStandardItem(parent, SequencePreset::Create(tr("%1 25 FPS").arg(name),
width,
height,
@@ -120,7 +120,7 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateHDPresetFolder(const QString &na
48000,
AV_CH_LAYOUT_STEREO,
divider,
- kDefaultPreviewFormat));
+ default_format));
AddStandardItem(parent, SequencePreset::Create(tr("%1 29.97 FPS").arg(name),
width,
height,
@@ -130,7 +130,7 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateHDPresetFolder(const QString &na
48000,
AV_CH_LAYOUT_STEREO,
divider,
- kDefaultPreviewFormat));
+ default_format));
AddStandardItem(parent, SequencePreset::Create(tr("%1 50 FPS").arg(name),
width,
height,
@@ -140,7 +140,7 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateHDPresetFolder(const QString &na
48000,
AV_CH_LAYOUT_STEREO,
divider,
- kDefaultPreviewFormat));
+ default_format));
AddStandardItem(parent, SequencePreset::Create(tr("%1 59.94 FPS").arg(name),
width,
height,
@@ -150,12 +150,13 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateHDPresetFolder(const QString &na
48000,
AV_CH_LAYOUT_STEREO,
divider,
- kDefaultPreviewFormat));
+ default_format));
return parent;
}
QTreeWidgetItem *SequenceDialogPresetTab::CreateSDPresetFolder(const QString &name, int width, int height, const rational& frame_rate, const rational &standard_par, const rational &wide_par, int divider)
{
+ VideoParams::Format default_format = static_cast(Config::Current()["OfflinePixelFormat"].toInt());
QTreeWidgetItem* parent = CreateFolder(name);
preset_tree_->addTopLevelItem(parent);
AddStandardItem(parent, SequencePreset::Create(tr("%1 Standard").arg(name),
@@ -167,7 +168,7 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateSDPresetFolder(const QString &na
48000,
AV_CH_LAYOUT_STEREO,
divider,
- kDefaultPreviewFormat));
+ default_format));
AddStandardItem(parent, SequencePreset::Create(tr("%1 Widescreen").arg(name),
width,
height,
@@ -177,7 +178,7 @@ QTreeWidgetItem *SequenceDialogPresetTab::CreateSDPresetFolder(const QString &na
48000,
AV_CH_LAYOUT_STEREO,
divider,
- kDefaultPreviewFormat));
+ default_format));
return parent;
}
diff --git a/app/dialog/sequence/sequencepreset.h b/app/dialog/sequence/sequencepreset.h
index 6b134b8f1..59db546fd 100644
--- a/app/dialog/sequence/sequencepreset.h
+++ b/app/dialog/sequence/sequencepreset.h
@@ -26,7 +26,6 @@
#include "common/rational.h"
#include "common/xmlutils.h"
#include "dialog/sequence/presetmanager.h"
-#include "render/pixelformat.h"
#include "render/videoparams.h"
OLIVE_NAMESPACE_ENTER
@@ -44,7 +43,7 @@ public:
int sample_rate,
uint64_t channel_layout,
int preview_divider,
- PixelFormat::Format preview_format) :
+ VideoParams::Format preview_format) :
width_(width),
height_(height),
frame_rate_(frame_rate),
@@ -67,7 +66,7 @@ public:
int sample_rate,
uint64_t channel_layout,
int preview_divider,
- PixelFormat::Format preview_format)
+ VideoParams::Format preview_format)
{
return std::make_shared(name, width, height, frame_rate, pixel_aspect,
interlacing, sample_rate, channel_layout,
@@ -96,7 +95,7 @@ public:
} else if (reader->name() == QStringLiteral("divider")) {
preview_divider_ = reader->readElementText().toInt();
} else if (reader->name() == QStringLiteral("format")) {
- preview_format_ = static_cast(reader->readElementText().toInt());
+ preview_format_ = static_cast(reader->readElementText().toInt());
} else {
reader->skipCurrentElement();
}
@@ -157,7 +156,7 @@ public:
return preview_divider_;
}
- PixelFormat::Format preview_format() const
+ VideoParams::Format preview_format() const
{
return preview_format_;
}
@@ -171,7 +170,7 @@ private:
int sample_rate_;
uint64_t channel_layout_;
int preview_divider_;
- PixelFormat::Format preview_format_;
+ VideoParams::Format preview_format_;
};
diff --git a/app/node/audio/pan/pan.cpp b/app/node/audio/pan/pan.cpp
index dad795d02..7fbfd99ca 100644
--- a/app/node/audio/pan/pan.cpp
+++ b/app/node/audio/pan/pan.cpp
@@ -49,7 +49,7 @@ QString PanNode::id() const
return QStringLiteral("org.olivevideoeditor.Olive.pan");
}
-QList PanNode::Category() const
+QVector PanNode::Category() const
{
return {kCategoryChannels};
}
@@ -69,7 +69,7 @@ NodeValueTable PanNode::Value(NodeValueDatabase &value) const
NodeValueTable table = value.Merge();
if (job.HasSamples()) {
- float pan_volume = job.GetValue(panning_input_).data().toFloat();
+ float pan_volume = job.GetValue(panning_input_).data.toFloat();
if (panning_input_->is_static()) {
if (!qIsNull(pan_volume) && job.samples()->audio_params().channel_count() == 2) {
if (pan_volume > 0) {
diff --git a/app/node/audio/pan/pan.h b/app/node/audio/pan/pan.h
index df23f9d0f..9ec58b110 100644
--- a/app/node/audio/pan/pan.h
+++ b/app/node/audio/pan/pan.h
@@ -34,7 +34,7 @@ public:
virtual QString Name() const override;
virtual QString id() const override;
- virtual QList Category() const override;
+ virtual QVector Category() const override;
virtual QString Description() const override;
virtual NodeValueTable Value(NodeValueDatabase &value) const override;
diff --git a/app/node/audio/volume/volume.cpp b/app/node/audio/volume/volume.cpp
index 4b403118b..b6d48e764 100644
--- a/app/node/audio/volume/volume.cpp
+++ b/app/node/audio/volume/volume.cpp
@@ -49,7 +49,7 @@ QString VolumeNode::id() const
return QStringLiteral("org.olivevideoeditor.Olive.volume");
}
-QList VolumeNode::Category() const
+QVector VolumeNode::Category() const
{
return {kCategoryFilter};
}
diff --git a/app/node/audio/volume/volume.h b/app/node/audio/volume/volume.h
index e408e3be7..c17656241 100644
--- a/app/node/audio/volume/volume.h
+++ b/app/node/audio/volume/volume.h
@@ -34,7 +34,7 @@ public:
virtual QString Name() const override;
virtual QString id() const override;
- virtual QList Category() const override;
+ virtual QVector Category() const override;
virtual QString Description() const override;
virtual NodeValueTable Value(NodeValueDatabase &value) const override;
diff --git a/app/node/block/block.cpp b/app/node/block/block.cpp
index 68b9779bd..0289e11b2 100644
--- a/app/node/block/block.cpp
+++ b/app/node/block/block.cpp
@@ -58,7 +58,7 @@ Block::Block() :
set_length_and_media_out(1);
}
-QList Block::Category() const
+QVector Block::Category() const
{
return {kCategoryTimeline};
}
@@ -241,9 +241,9 @@ void Block::SaveInternal(QXmlStreamWriter *writer) const
}
}
-QList Block::GetInputsToHash() const
+QVector Block::GetInputsToHash() const
{
- QList inputs = Node::GetInputsToHash();
+ QVector inputs = Node::GetInputsToHash();
// Ignore these inputs
inputs.removeOne(media_in_input_);
diff --git a/app/node/block/block.h b/app/node/block/block.h
index ca5c700ee..52beed7cb 100644
--- a/app/node/block/block.h
+++ b/app/node/block/block.h
@@ -43,7 +43,7 @@ public:
virtual Type type() const = 0;
- virtual QList Category() const override;
+ virtual QVector Category() const override;
const rational& in() const;
const rational& out() const;
@@ -110,7 +110,7 @@ protected:
virtual void SaveInternal(QXmlStreamWriter* writer) const override;
- virtual QList GetInputsToHash() const override;
+ virtual QVector GetInputsToHash() const override;
virtual void LengthChangedEvent(const rational& old_length,
const rational& new_length,
diff --git a/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp b/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp
index 2dfe7ace4..c85d48d3e 100644
--- a/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp
+++ b/app/node/block/transition/crossdissolve/crossdissolvetransition.cpp
@@ -42,7 +42,7 @@ QString CrossDissolveTransition::id() const
return QStringLiteral("org.olivevideoeditor.Olive.crossdissolve");
}
-QList CrossDissolveTransition::Category() const
+QVector CrossDissolveTransition::Category() const
{
return {kCategoryTransition};
}
@@ -56,7 +56,7 @@ ShaderCode CrossDissolveTransition::GetShaderCode(const QString &shader_id) cons
{
Q_UNUSED(shader_id)
- return ShaderCode(Node::ReadFileAsString(":/shaders/crossdissolve.frag"), QString());
+ return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/crossdissolve.frag"), QString());
}
void CrossDissolveTransition::SampleJobEvent(SampleBufferPtr from_samples, SampleBufferPtr to_samples, SampleBufferPtr out_samples, double time_in) const
diff --git a/app/node/block/transition/crossdissolve/crossdissolvetransition.h b/app/node/block/transition/crossdissolve/crossdissolvetransition.h
index e45d1de67..98daf4330 100644
--- a/app/node/block/transition/crossdissolve/crossdissolvetransition.h
+++ b/app/node/block/transition/crossdissolve/crossdissolvetransition.h
@@ -34,7 +34,7 @@ public:
virtual QString Name() const override;
virtual QString id() const override;
- virtual QList Category() const override;
+ virtual QVector Category() const override;
virtual QString Description() const override;
//virtual void Retranslate() override;
diff --git a/app/node/block/transition/diptocolor/diptocolortransition.cpp b/app/node/block/transition/diptocolor/diptocolortransition.cpp
index 816c75011..d75593158 100644
--- a/app/node/block/transition/diptocolor/diptocolortransition.cpp
+++ b/app/node/block/transition/diptocolor/diptocolortransition.cpp
@@ -43,7 +43,7 @@ QString DipToColorTransition::id() const
return QStringLiteral("org.olivevideoeditor.Olive.diptocolor");
}
-QList DipToColorTransition::Category() const
+QVector DipToColorTransition::Category() const
{
return {kCategoryTransition};
}
@@ -57,7 +57,7 @@ ShaderCode DipToColorTransition::GetShaderCode(const QString &shader_id) const
{
Q_UNUSED(shader_id)
- return ShaderCode(Node::ReadFileAsString(":/shaders/diptoblack.frag"), QString());
+ return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/diptoblack.frag"), QString());
}
void DipToColorTransition::ShaderJobEvent(NodeValueDatabase &value, ShaderJob &job) const
diff --git a/app/node/block/transition/diptocolor/diptocolortransition.h b/app/node/block/transition/diptocolor/diptocolortransition.h
index 2c19443e1..660b5fcfc 100644
--- a/app/node/block/transition/diptocolor/diptocolortransition.h
+++ b/app/node/block/transition/diptocolor/diptocolortransition.h
@@ -34,7 +34,7 @@ public:
virtual QString Name() const override;
virtual QString id() const override;
- virtual QList Category() const override;
+ virtual QVector Category() const override;
virtual QString Description() const override;
virtual ShaderCode GetShaderCode(const QString& shader_id) const override;
diff --git a/app/node/block/transition/transition.cpp b/app/node/block/transition/transition.cpp
index 3b339f536..5fc6a863a 100644
--- a/app/node/block/transition/transition.cpp
+++ b/app/node/block/transition/transition.cpp
@@ -161,15 +161,15 @@ void TransitionBlock::InsertTransitionTimes(AcceleratedJob *job, const double &t
{
// Provides total transition progress from 0.0 (start) - 1.0 (end)
job->InsertValue(QStringLiteral("ove_tprog_all"),
- NodeValue(NodeParam::kFloat, GetTotalProgress(time), this));
+ ShaderValue(GetTotalProgress(time), NodeParam::kFloat));
// Provides progress of out section from 1.0 (start) - 0.0 (end)
job->InsertValue(QStringLiteral("ove_tprog_out"),
- NodeValue(NodeParam::kFloat, GetOutProgress(time), this));
+ ShaderValue(GetOutProgress(time), NodeParam::kFloat));
// Provides progress of in section from 0.0 (start) - 1.0 (end)
job->InsertValue(QStringLiteral("ove_tprog_in"),
- NodeValue(NodeParam::kFloat, GetInProgress(time), this));
+ ShaderValue(GetInProgress(time), NodeParam::kFloat));
}
void TransitionBlock::BlockConnected(NodeEdgePtr edge)
diff --git a/app/node/filter/blur/blur.cpp b/app/node/filter/blur/blur.cpp
index 7950f77e9..332a31555 100644
--- a/app/node/filter/blur/blur.cpp
+++ b/app/node/filter/blur/blur.cpp
@@ -59,7 +59,7 @@ QString BlurFilterNode::id() const
return QStringLiteral("org.olivevideoeditor.Olive.blur");
}
-QList BlurFilterNode::Category() const
+QVector BlurFilterNode::Category() const
{
return {kCategoryFilter};
}
@@ -83,7 +83,7 @@ void BlurFilterNode::Retranslate()
ShaderCode BlurFilterNode::GetShaderCode(const QString &shader_id) const
{
Q_UNUSED(shader_id)
- return ShaderCode(ReadFileAsString(":/shaders/blur.frag"), QString());
+ return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/blur.frag"), QString());
}
NodeValueTable BlurFilterNode::Value(NodeValueDatabase &value) const
@@ -100,19 +100,19 @@ NodeValueTable BlurFilterNode::Value(NodeValueDatabase &value) const
NodeValueTable table = value.Merge();
// If there's no texture, no need to run an operation
- if (!job.GetValue(texture_input_).data().isNull()) {
+ if (!job.GetValue(texture_input_).data.isNull()) {
// Check if radius > 0, and both "horiz" and/or "vert" are enabled
- if ((job.GetValue(horiz_input_).data().toBool() || job.GetValue(vert_input_).data().toBool())
- && job.GetValue(radius_input_).data().toDouble() > 0.0) {
+ if ((job.GetValue(horiz_input_).data.toBool() || job.GetValue(vert_input_).data.toBool())
+ && job.GetValue(radius_input_).data.toDouble() > 0.0) {
// Set iteration count to 2 if we're blurring both horizontally and vertically
- if (job.GetValue(horiz_input_).data().toBool() && job.GetValue(vert_input_).data().toBool()) {
+ if (job.GetValue(horiz_input_).data.toBool() && job.GetValue(vert_input_).data.toBool()) {
job.SetIterations(2, texture_input_);
}
// If we're not repeating pixels, expect an alpha channel to appear
- if (!job.GetValue(repeat_edge_pixels_input_).data().toBool()) {
+ if (!job.GetValue(repeat_edge_pixels_input_).data.toBool()) {
job.SetAlphaChannelRequired(true);
}
@@ -120,7 +120,7 @@ NodeValueTable BlurFilterNode::Value(NodeValueDatabase &value) const
} else {
// If we're not performing the blur job, just push the texture
- table.Push(job.GetValue(texture_input_));
+ table.Push(job.GetValue(texture_input_), this);
}
}
diff --git a/app/node/filter/blur/blur.h b/app/node/filter/blur/blur.h
index 6f9100605..088363991 100644
--- a/app/node/filter/blur/blur.h
+++ b/app/node/filter/blur/blur.h
@@ -34,7 +34,7 @@ public:
virtual QString Name() const override;
virtual QString id() const override;
- virtual QList Category() const override;
+ virtual QVector Category() const override;
virtual QString Description() const override;
virtual void Retranslate() override;
diff --git a/app/node/filter/stroke/stroke.cpp b/app/node/filter/stroke/stroke.cpp
index d62c99b37..5606c19a8 100644
--- a/app/node/filter/stroke/stroke.cpp
+++ b/app/node/filter/stroke/stroke.cpp
@@ -63,7 +63,7 @@ QString StrokeFilterNode::id() const
return QStringLiteral("org.olivevideoeditor.Olive.stroke");
}
-QList StrokeFilterNode::Category() const
+QVector StrokeFilterNode::Category() const
{
return {kCategoryFilter};
}
@@ -94,12 +94,12 @@ NodeValueTable StrokeFilterNode::Value(NodeValueDatabase &value) const
NodeValueTable table = value.Merge();
- if (!job.GetValue(tex_input_).data().isNull()) {
- if (job.GetValue(radius_input_).data().toDouble() > 0.0
- && job.GetValue(opacity_input_).data().toDouble() > 0.0) {
+ if (!job.GetValue(tex_input_).data.isNull()) {
+ if (job.GetValue(radius_input_).data.toDouble() > 0.0
+ && job.GetValue(opacity_input_).data.toDouble() > 0.0) {
table.Push(NodeParam::kShaderJob, QVariant::fromValue(job), this);
} else {
- table.Push(job.GetValue(tex_input_));
+ table.Push(job.GetValue(tex_input_), this);
}
}
@@ -110,7 +110,7 @@ ShaderCode StrokeFilterNode::GetShaderCode(const QString &shader_id) const
{
Q_UNUSED(shader_id)
- return ShaderCode(ReadFileAsString(":/shaders/stroke.frag"), QString());
+ return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/stroke.frag"), QString());
}
OLIVE_NAMESPACE_EXIT
diff --git a/app/node/filter/stroke/stroke.h b/app/node/filter/stroke/stroke.h
index b9d1ac993..044f9ba95 100644
--- a/app/node/filter/stroke/stroke.h
+++ b/app/node/filter/stroke/stroke.h
@@ -34,7 +34,7 @@ public:
virtual QString Name() const override;
virtual QString id() const override;
- virtual QList Category() const override;
+ virtual QVector Category() const override;
virtual QString Description() const override;
virtual void Retranslate() override;
diff --git a/app/node/generator/matrix/matrix.cpp b/app/node/generator/matrix/matrix.cpp
index b35247276..836510a3f 100644
--- a/app/node/generator/matrix/matrix.cpp
+++ b/app/node/generator/matrix/matrix.cpp
@@ -72,7 +72,7 @@ QString MatrixGenerator::id() const
return QStringLiteral("org.olivevideoeditor.Olive.transform");
}
-QList MatrixGenerator::Category() const
+QVector MatrixGenerator::Category() const
{
return {kCategoryGenerator, kCategoryMath};
}
diff --git a/app/node/generator/matrix/matrix.h b/app/node/generator/matrix/matrix.h
index d6418eda8..ff1ddddb6 100644
--- a/app/node/generator/matrix/matrix.h
+++ b/app/node/generator/matrix/matrix.h
@@ -39,7 +39,7 @@ public:
virtual QString Name() const override;
virtual QString ShortName() const override;
virtual QString id() const override;
- virtual QList Category() const override;
+ virtual QVector Category() const override;
virtual QString Description() const override;
virtual void Retranslate() override;
diff --git a/app/node/generator/polygon/polygon.cpp b/app/node/generator/polygon/polygon.cpp
index 870488cc0..379eb659f 100644
--- a/app/node/generator/polygon/polygon.cpp
+++ b/app/node/generator/polygon/polygon.cpp
@@ -69,7 +69,7 @@ QString PolygonGenerator::id() const
return QStringLiteral("org.olivevideoeditor.Olive.polygon");
}
-QList PolygonGenerator::Category() const
+QVector PolygonGenerator::Category() const
{
return {kCategoryGenerator};
}
@@ -89,7 +89,7 @@ ShaderCode PolygonGenerator::GetShaderCode(const QString &shader_id) const
{
Q_UNUSED(shader_id)
- return ShaderCode(Node::ReadFileAsString(":/shaders/polygon.frag"), QString());
+ return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/polygon.frag"), QString());
}
NodeValueTable PolygonGenerator::Value(NodeValueDatabase &value) const
diff --git a/app/node/generator/polygon/polygon.h b/app/node/generator/polygon/polygon.h
index 296177dea..d764ae7be 100644
--- a/app/node/generator/polygon/polygon.h
+++ b/app/node/generator/polygon/polygon.h
@@ -35,7 +35,7 @@ public:
virtual QString Name() const override;
virtual QString id() const override;
- virtual QList Category() const override;
+ virtual QVector Category() const override;
virtual QString Description() const override;
virtual void Retranslate() override;
diff --git a/app/node/generator/solid/solid.cpp b/app/node/generator/solid/solid.cpp
index abcf6491f..9309dcd69 100644
--- a/app/node/generator/solid/solid.cpp
+++ b/app/node/generator/solid/solid.cpp
@@ -48,7 +48,7 @@ QString SolidGenerator::id() const
return QStringLiteral("org.olivevideoeditor.Olive.solidgenerator");
}
-QList SolidGenerator::Category() const
+QVector SolidGenerator::Category() const
{
return {kCategoryGenerator};
}
@@ -77,7 +77,7 @@ ShaderCode SolidGenerator::GetShaderCode(const QString &shader_id) const
{
Q_UNUSED(shader_id)
- return ShaderCode(ReadFileAsString(":/shaders/solid.frag"), QString());
+ return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/solid.frag"), QString());
}
OLIVE_NAMESPACE_EXIT
diff --git a/app/node/generator/solid/solid.h b/app/node/generator/solid/solid.h
index aa6fb5f79..101c8f15a 100644
--- a/app/node/generator/solid/solid.h
+++ b/app/node/generator/solid/solid.h
@@ -34,7 +34,7 @@ public:
virtual QString Name() const override;
virtual QString id() const override;
- virtual QList Category() const override;
+ virtual QVector Category() const override;
virtual QString Description() const override;
virtual void Retranslate() override;
diff --git a/app/node/generator/text/text.cpp b/app/node/generator/text/text.cpp
index de38f08bd..3c078cf34 100644
--- a/app/node/generator/text/text.cpp
+++ b/app/node/generator/text/text.cpp
@@ -72,7 +72,7 @@ QString TextGenerator::id() const
return QStringLiteral("org.olivevideoeditor.Olive.textgenerator");
}
-QList TextGenerator::Category() const
+QVector TextGenerator::Category() const
{
return {kCategoryGenerator};
}
@@ -104,7 +104,7 @@ NodeValueTable TextGenerator::Value(NodeValueDatabase &value) const
NodeValueTable table = value.Merge();
- if (!job.GetValue(text_input_).data().toString().isEmpty()) {
+ if (!job.GetValue(text_input_).data.toString().isEmpty()) {
table.Push(NodeParam::kGenerateJob, QVariant::fromValue(job), this);
}
@@ -124,14 +124,14 @@ void TextGenerator::GenerateFrame(FramePtr frame, const GenerateJob& job) const
// Set default font
QFont default_font;
- default_font.setFamily(job.GetValue(font_input_).data().toString());
- default_font.setPointSizeF(job.GetValue(font_size_input_).data().toFloat());
+ default_font.setFamily(job.GetValue(font_input_).data.toString());
+ default_font.setPointSizeF(job.GetValue(font_size_input_).data.toFloat());
text_doc.setDefaultFont(default_font);
// Center by default
text_doc.setDefaultTextOption(QTextOption(Qt::AlignCenter));
- text_doc.setHtml(job.GetValue(text_input_).data().toString());
+ text_doc.setHtml(job.GetValue(text_input_).data.toString());
// Align to 80% width because that's considered the "title safe" area
int tenth_of_width = frame->video_params().width() / 10;
@@ -144,7 +144,7 @@ void TextGenerator::GenerateFrame(FramePtr frame, const GenerateJob& job) const
// Push 10% inwards to compensate for title safe area
p.translate(tenth_of_width, 0);
- TextVerticalAlign valign = static_cast(job.GetValue(valign_input_).data().toInt());
+ TextVerticalAlign valign = static_cast(job.GetValue(valign_input_).data.toInt());
int doc_height = text_doc.size().height();
switch (valign) {
@@ -165,7 +165,7 @@ void TextGenerator::GenerateFrame(FramePtr frame, const GenerateJob& job) const
text_doc.drawContents(&p);
// Transplant alpha channel to frame
- Color rgb = job.GetValue(color_input_).data().value();
+ Color rgb = job.GetValue(color_input_).data.value();
for (int x=0; xwidth(); x++) {
for (int y=0; yheight(); y++) {
uchar src_alpha = img.bits()[img.bytesPerLine() * y + x];
diff --git a/app/node/generator/text/text.h b/app/node/generator/text/text.h
index e4a14b868..cb80fd035 100644
--- a/app/node/generator/text/text.h
+++ b/app/node/generator/text/text.h
@@ -34,7 +34,7 @@ public:
virtual QString Name() const override;
virtual QString id() const override;
- virtual QList Category() const override;
+ virtual QVector Category() const override;
virtual QString Description() const override;
virtual void Retranslate() override;
diff --git a/app/node/input.cpp b/app/node/input.cpp
index de31c0934..d34e777b6 100644
--- a/app/node/input.cpp
+++ b/app/node/input.cpp
@@ -423,7 +423,7 @@ QVariant NodeInput::StringToValue(const DataType& data_type, const QString &stri
}
}
-void NodeInput::GetDependencies(QList &list, bool traverse, bool exclusive_only) const
+void NodeInput::GetDependencies(QVector &list, bool traverse, bool exclusive_only) const
{
if (is_connected()
&& (get_connected_output()->edges().size() == 1 || !exclusive_only)) {
@@ -433,7 +433,7 @@ void NodeInput::GetDependencies(QList &list, bool traverse, bool exclusi
list.append(connected);
if (traverse) {
- QList connected_inputs = connected->GetInputsIncludingArrays();
+ QVector connected_inputs = connected->GetInputsIncludingArrays();
foreach (NodeInput* i, connected_inputs) {
i->GetDependencies(list, traverse, exclusive_only);
@@ -461,21 +461,21 @@ QVariant NodeInput::GetDefaultValueForTrack(int track) const
return default_value_.at(track);
}
-QList NodeInput::GetDependencies(bool traverse, bool exclusive_only) const
+QVector NodeInput::GetDependencies(bool traverse, bool exclusive_only) const
{
- QList list;
+ QVector list;
GetDependencies(list, traverse, exclusive_only);
return list;
}
-QList NodeInput::GetExclusiveDependencies() const
+QVector NodeInput::GetExclusiveDependencies() const
{
return GetDependencies(true, true);
}
-QList NodeInput::GetImmediateDependencies() const
+QVector NodeInput::GetImmediateDependencies() const
{
return GetDependencies(false, false);
}
diff --git a/app/node/input.h b/app/node/input.h
index 2a81c6d40..c7296ad3e 100644
--- a/app/node/input.h
+++ b/app/node/input.h
@@ -282,17 +282,17 @@ public:
static QVariant StringToValue(const DataType &data_type, const QString &string, bool value_is_a_key_track);
- void GetDependencies(QList& list, bool traverse, bool exclusive_only) const;
+ void GetDependencies(QVector &list, bool traverse, bool exclusive_only) const;
QVariant GetDefaultValue() const;
QVariant GetDefaultValueForTrack(int track) const;
- QList GetDependencies(bool traverse = true, bool exclusive_only = false) const;
+ QVector GetDependencies(bool traverse = true, bool exclusive_only = false) const;
- QList GetExclusiveDependencies() const;
+ QVector GetExclusiveDependencies() const;
- QList GetImmediateDependencies() const;
+ QVector GetImmediateDependencies() const;
signals:
void ValueChanged(const OLIVE_NAMESPACE::TimeRange& range);
diff --git a/app/node/input/media/media.cpp b/app/node/input/media/media.cpp
index 8d5acd4b8..5469ef46c 100644
--- a/app/node/input/media/media.cpp
+++ b/app/node/input/media/media.cpp
@@ -35,7 +35,7 @@ MediaInput::MediaInput() :
AddInput(footage_input_);
}
-QList MediaInput::Category() const
+QVector MediaInput::Category() const
{
return {kCategoryInput};
}
diff --git a/app/node/input/media/media.h b/app/node/input/media/media.h
index 1ed9e0889..895a4ff66 100644
--- a/app/node/input/media/media.h
+++ b/app/node/input/media/media.h
@@ -38,7 +38,7 @@ public:
virtual Stream::Type type() const = 0;
- virtual QList Category() const override;
+ virtual QVector Category() const override;
StreamPtr stream();
void SetStream(StreamPtr s);
diff --git a/app/node/input/media/video/video.cpp b/app/node/input/media/video/video.cpp
index c02aeaf39..5507bc12c 100644
--- a/app/node/input/media/video/video.cpp
+++ b/app/node/input/media/video/video.cpp
@@ -27,7 +27,6 @@
#include "codec/ffmpeg/ffmpegdecoder.h"
#include "core.h"
#include "project/item/footage/footage.h"
-#include "render/pixelformat.h"
OLIVE_NAMESPACE_ENTER
@@ -38,7 +37,7 @@ Node *VideoInput::copy() const
Stream::Type VideoInput::type() const
{
- return Stream::kVideo;
+ return Stream::kVideo;
}
QString VideoInput::Name() const
diff --git a/app/node/input/time/timeinput.cpp b/app/node/input/time/timeinput.cpp
index d4df5b32b..485e90173 100644
--- a/app/node/input/time/timeinput.cpp
+++ b/app/node/input/time/timeinput.cpp
@@ -41,7 +41,7 @@ QString TimeInput::id() const
return QStringLiteral("org.olivevideoeditor.Olive.time");
}
-QList TimeInput::Category() const
+QVector TimeInput::Category() const
{
return {kCategoryInput};
}
diff --git a/app/node/input/time/timeinput.h b/app/node/input/time/timeinput.h
index e79d1b333..d41492942 100644
--- a/app/node/input/time/timeinput.h
+++ b/app/node/input/time/timeinput.h
@@ -35,7 +35,7 @@ public:
virtual QString Name() const override;
virtual QString id() const override;
- virtual QList Category() const override;
+ virtual QVector Category() const override;
virtual QString Description() const override;
virtual NodeValueTable Value(NodeValueDatabase& value) const override;
diff --git a/app/node/math/math/math.cpp b/app/node/math/math/math.cpp
index 9330d64d3..fc43f7010 100644
--- a/app/node/math/math/math.cpp
+++ b/app/node/math/math/math.cpp
@@ -55,7 +55,7 @@ QString MathNode::id() const
return QStringLiteral("org.olivevideoeditor.Olive.math");
}
-QList MathNode::Category() const
+QVector MathNode::Category() const
{
return {kCategoryMath};
}
diff --git a/app/node/math/math/math.h b/app/node/math/math/math.h
index 81e5111b1..97edcdf0b 100644
--- a/app/node/math/math/math.h
+++ b/app/node/math/math/math.h
@@ -34,7 +34,7 @@ public:
virtual QString Name() const override;
virtual QString id() const override;
- virtual QList Category() const override;
+ virtual QVector Category() const override;
virtual QString Description() const override;
virtual void Retranslate() override;
diff --git a/app/node/math/math/mathbase.cpp b/app/node/math/math/mathbase.cpp
index 731a750da..e6b980c66 100644
--- a/app/node/math/math/mathbase.cpp
+++ b/app/node/math/math/mathbase.cpp
@@ -48,7 +48,7 @@ ShaderCode MathNodeBase::GetShaderCodeInternal(const QString &shader_id, NodeInp
// No-op frag shader (can we return QString() instead?)
operation = QStringLiteral("texture(%1, ove_texcoord)").arg(tex_in->id());
- vert = ReadFileAsString(":/shaders/matrix.vert").arg(mat_in->id(), tex_in->id());
+ vert = FileFunctions::ReadFileAsString(":/shaders/matrix.vert").arg(mat_in->id(), tex_in->id());
} else {
switch (op) {
@@ -329,7 +329,7 @@ NodeValueTable MathNodeBase::ValueInternal(NodeValueDatabase &value, Operation o
float number = RetrieveNumber(number_val);
SampleJob job(val_a.type() == NodeParam::kSamples ? val_a : val_b);
- job.InsertValue(number_param, NodeValue(NodeParam::kFloat, number, this));
+ job.InsertValue(number_param, ShaderValue(number, NodeParam::kFloat));
if (job.HasSamples()) {
if (number_param->is_static()) {
diff --git a/app/node/math/merge/merge.cpp b/app/node/math/merge/merge.cpp
index 092b69455..6b7ab17a6 100644
--- a/app/node/math/merge/merge.cpp
+++ b/app/node/math/merge/merge.cpp
@@ -46,7 +46,7 @@ QString MergeNode::id() const
return QStringLiteral("org.olivevideoeditor.Olive.merge");
}
-QList MergeNode::Category() const
+QVector MergeNode::Category() const
{
return {kCategoryMath};
}
@@ -66,7 +66,7 @@ ShaderCode MergeNode::GetShaderCode(const QString &shader_id) const
{
Q_UNUSED(shader_id)
- return ShaderCode(ReadFileAsString(":/shaders/alphaover.frag"), QString());
+ return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/alphaover.frag"), QString());
}
NodeValueTable MergeNode::Value(NodeValueDatabase &value) const
@@ -75,17 +75,18 @@ NodeValueTable MergeNode::Value(NodeValueDatabase &value) const
job.InsertValue(base_in_, value);
job.InsertValue(blend_in_, value);
- // FIXME: Check if "blend" is RGB-only, in which case it's a no-op
-
NodeValueTable table = value.Merge();
- if (!job.GetValue(base_in_).data().isNull() || !job.GetValue(blend_in_).data().isNull()) {
- if (job.GetValue(base_in_).data().isNull()) {
- // We only have a blend texture, no need to alpha over
- table.Push(job.GetValue(blend_in_));
- } else if (job.GetValue(blend_in_).data().isNull()) {
+ TexturePtr base_tex = job.GetValue(base_in_).data.value();
+ TexturePtr blend_tex = job.GetValue(blend_in_).data.value();
+
+ if (base_tex || blend_tex) {
+ if (!base_tex || (blend_tex && blend_tex->channel_count() < VideoParams::kRGBAChannelCount)) {
+ // We only have a blend texture or the blend texture is RGB only, no need to alpha over
+ table.Push(job.GetValue(blend_in_), this);
+ } else if (!blend_tex) {
// We only have a base texture, no need to alpha over
- table.Push(job.GetValue(base_in_));
+ table.Push(job.GetValue(base_in_), this);
} else {
// We have both textures, push the job
table.Push(NodeParam::kShaderJob, QVariant::fromValue(job), this);
diff --git a/app/node/math/merge/merge.h b/app/node/math/merge/merge.h
index 70f79369b..57182caaf 100644
--- a/app/node/math/merge/merge.h
+++ b/app/node/math/merge/merge.h
@@ -34,7 +34,7 @@ public:
virtual QString Name() const override;
virtual QString id() const override;
- virtual QList Category() const override;
+ virtual QVector Category() const override;
virtual QString Description() const override;
virtual void Retranslate() override;
diff --git a/app/node/math/trigonometry/trigonometry.cpp b/app/node/math/trigonometry/trigonometry.cpp
index c2f0d03ff..a8649ff64 100644
--- a/app/node/math/trigonometry/trigonometry.cpp
+++ b/app/node/math/trigonometry/trigonometry.cpp
@@ -48,7 +48,7 @@ QString TrigonometryNode::id() const
return QStringLiteral("org.olivevideoeditor.Olive.trigonometry");
}
-QList TrigonometryNode::Category() const
+QVector TrigonometryNode::Category() const
{
return {kCategoryMath};
}
diff --git a/app/node/math/trigonometry/trigonometry.h b/app/node/math/trigonometry/trigonometry.h
index 9af68472f..b1524616a 100644
--- a/app/node/math/trigonometry/trigonometry.h
+++ b/app/node/math/trigonometry/trigonometry.h
@@ -34,7 +34,7 @@ public:
virtual QString Name() const override;
virtual QString id() const override;
- virtual QList Category() const override;
+ virtual QVector Category() const override;
virtual QString Description() const override;
virtual void Retranslate() override;
diff --git a/app/node/node.cpp b/app/node/node.cpp
index 148c41ee5..0abc3e69d 100644
--- a/app/node/node.cpp
+++ b/app/node/node.cpp
@@ -29,6 +29,7 @@
#include "project/project.h"
#include "project/item/footage/footage.h"
#include "project/item/footage/videostream.h"
+#include "widget/nodeview/nodeviewundo.h"
OLIVE_NAMESPACE_ENTER
@@ -239,6 +240,66 @@ TimeRange Node::OutputTimeAdjustment(NodeInput *, const TimeRange &input_time) c
return input_time;
}
+QVector Node::CopyDependencyGraph(const QVector &nodes, QUndoCommand* command)
+{
+ int nb_nodes = nodes.size();
+
+ QVector copies(nb_nodes);
+
+ for (int i=0; icopy();;
+
+ // Copy the values, but NOT the connections, since we'll be connecting to our own clones later
+ Node::CopyInputs(nodes.at(i), c, false);
+
+ // Add to graph
+ NodeGraph* graph = static_cast(nodes.at(i)->parent());
+ if (command) {
+ new NodeAddCommand(graph, c, command);
+ } else {
+ graph->AddNode(c);
+ }
+
+ // Store in array at the same index as source
+ copies[i] = c;
+ }
+
+ CopyDependencyGraph(nodes, copies, command);
+
+ return copies;
+}
+
+void Node::CopyDependencyGraph(const QVector