From f3b60369aa756e575c6905dd2fb2505b39927aa1 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Thu, 8 Aug 2019 15:38:58 +1000 Subject: [PATCH] use IDs for multiple decoders --- app/decoder/decoder.h | 24 ++++++++- app/decoder/ffmpeg/ffmpegdecoder.cpp | 65 +++++++++++++++++------- app/decoder/ffmpeg/ffmpegdecoder.h | 8 +++ app/decoder/frame.h | 10 ++++ app/decoder/probeserver.cpp | 76 +++++++++++++++++++++++----- app/decoder/probeserver.h | 3 ++ app/node/node.h | 41 +++++++-------- app/project/item/footage/footage.cpp | 10 ++++ app/project/item/footage/footage.h | 19 +++++++ 9 files changed, 203 insertions(+), 53 deletions(-) diff --git a/app/decoder/decoder.h b/app/decoder/decoder.h index 4fd823f78..0745f3b07 100644 --- a/app/decoder/decoder.h +++ b/app/decoder/decoder.h @@ -48,13 +48,35 @@ class Decoder : public QObject { Q_OBJECT public: - Decoder(); Decoder(Stream* fs); + // Necessary for subclassing, it's empty virtual ~Decoder(); + /** + * @brief Deleted copy constructor + */ + Decoder(const Decoder& other) = delete; + + /** + * @brief Deleted move constructor + */ + Decoder(Decoder&& other) = delete; + + /** + * @brief Deleted copy assignment + */ + Decoder& operator=(const Decoder& other) = delete; + + /** + * @brief Deleted move assignment + */ + Decoder& operator=(Decoder&& other) = delete; + + virtual QString id() = 0; + const Stream* stream(); void set_stream(const Stream *fs); diff --git a/app/decoder/ffmpeg/ffmpegdecoder.cpp b/app/decoder/ffmpeg/ffmpegdecoder.cpp index 01e11012b..06d61ce79 100644 --- a/app/decoder/ffmpeg/ffmpegdecoder.cpp +++ b/app/decoder/ffmpeg/ffmpegdecoder.cpp @@ -44,6 +44,11 @@ FFmpegDecoder::FFmpegDecoder() : { } +FFmpegDecoder::~FFmpegDecoder() +{ + Close(); +} + bool FFmpegDecoder::Open() { if (open_) { @@ -192,27 +197,16 @@ FramePtr FFmpegDecoder::Retrieve(const rational &timecode, const rational &lengt // Convert timecode to AVStream timebase int64_t target_ts = qFloor(timecode.toDouble() * rational(avstream_->time_base).flipped().toDouble()); - // Index now if we haven't already - if (frame_index_.isEmpty() && !LoadFrameIndex()) { - Index(); + // Find closest actual timebase in the file + target_ts = GetClosestTimestampInIndex(target_ts); + + if (target_ts < 0) { + Error(tr("Index failed to produce a valid timestamp")); + return nullptr; } - // Use index to find closest frame in file - for (int i=1;i target_ts) { - target_ts = frame_index_.at(i - 1); - break; - } - } - - int ret = 0; - - // Allocate and init a packet for reading encoded data - AVPacket pkt; - av_init_packet(&pkt); - // Cache FFmpeg error code returns - ret = 0; + int ret = 0; // Set up seeking loop int64_t seek_ts = target_ts; @@ -225,7 +219,16 @@ FramePtr FFmpegDecoder::Retrieve(const rational &timecode, const rational &lengt if (frame_->pts > target_ts || frame_->pts == AV_NOPTS_VALUE) { avcodec_flush_buffers(codec_ctx_); av_seek_frame(fmt_ctx_, avstream_->index, seek_ts, AVSEEK_FLAG_BACKWARD); - seek_ts -= second_ts; + + // FFmpeg doesn't always seek correctly, if we have to seek again we wrangle it into seeking back far enough + + // If we already tried seeking to 0 though, there's nothing we can do so we error here + if (seek_ts == 0) { + Error(tr("FFmpeg failed to seek to the correct location")); + return nullptr; + } + + seek_ts = qMax(0L, seek_ts - second_ts); } ret = GetFrame(); @@ -307,6 +310,11 @@ void FFmpegDecoder::Close() open_ = false; } +QString FFmpegDecoder::id() +{ + return "ffmpeg"; +} + bool FFmpegDecoder::Probe(Footage *f) { if (open_) { @@ -564,3 +572,22 @@ AVPixelFormat FFmpegDecoder::GetCompatiblePixelFormat(const AVPixelFormat &pix_f 1, nullptr); } + +int64_t FFmpegDecoder::GetClosestTimestampInIndex(const int64_t &ts) +{ + // Index now if we haven't already + if (frame_index_.isEmpty() && !LoadFrameIndex()) { + Index(); + } + + // Use index to find closest frame in file + for (int i=1;i ts) { + return frame_index_.at(i - 1); + } + } + + return -1; +} diff --git a/app/decoder/ffmpeg/ffmpegdecoder.h b/app/decoder/ffmpeg/ffmpegdecoder.h index e4b8b03f4..8021edf89 100644 --- a/app/decoder/ffmpeg/ffmpegdecoder.h +++ b/app/decoder/ffmpeg/ffmpegdecoder.h @@ -37,14 +37,20 @@ extern "C" { class FFmpegDecoder : public Decoder { public: + // Constructor FFmpegDecoder(); + // Destructor + virtual ~FFmpegDecoder() override; + virtual bool Probe(Footage *f) override; virtual bool Open() override; virtual FramePtr Retrieve(const rational &timecode, const rational &length = 0) override; virtual void Close() override; + virtual QString id() override; + private: /** * @brief Handle an error @@ -118,6 +124,8 @@ private: */ AVPixelFormat GetCompatiblePixelFormat(const AVPixelFormat& pix_fmt); + int64_t GetClosestTimestampInIndex(const int64_t& ts); + AVFormatContext* fmt_ctx_; AVCodecContext* codec_ctx_; AVStream* avstream_; diff --git a/app/decoder/frame.h b/app/decoder/frame.h index 44149e5c0..a18dc8333 100644 --- a/app/decoder/frame.h +++ b/app/decoder/frame.h @@ -94,8 +94,18 @@ public: */ const uint8_t* const_data(); + /** + * @brief Allocate memory buffer to store data based on parameters + * + * For video frames, the width(), height(), and format() must be set for this function to work. + * + * If a memory buffer has been previously allocated without destroying, this function will destroy it. + */ void allocate(); + /** + * @brief Destroy a memory buffer allocated with allocate() + */ void destroy(); private: diff --git a/app/decoder/probeserver.cpp b/app/decoder/probeserver.cpp index 18e7cc966..25eb4a64e 100644 --- a/app/decoder/probeserver.cpp +++ b/app/decoder/probeserver.cpp @@ -26,6 +26,22 @@ #include "decoder/ffmpeg/ffmpegdecoder.h" +QVector ReceiveListOfAllDecoders() { + QVector decoders; + + decoders.append(new FFmpegDecoder()); + + return decoders; +} + +void FreeListOfDecoders(const QVector& decoders, Decoder* except = nullptr) { + foreach (Decoder* d, decoders) { + if (except == nullptr || except != d) { + delete d; + } + } +} + bool olive::ProbeMedia(Footage *f) { // Check for a valid filename @@ -43,27 +59,61 @@ bool olive::ProbeMedia(Footage *f) // Reset Footage state for probing f->Clear(); - // Create decoder instance - FFmpegDecoder ff_dec; - // Create list to iterate through - QList decoder_list; - decoder_list.append(&ff_dec); + QVector decoder_list = ReceiveListOfAllDecoders(); + + Decoder* found_decoder = nullptr; // Pass Footage through each Decoder's probe function for (int i=0;iProbe(f)) { - // FIXME Some way of "attaching" the Footage to the Decoder without having to iterate through Decoders again at - // render time? + Decoder* decoder = decoder_list.at(i); - f->set_status(Footage::kReady); - return true; + if (decoder->Probe(f)) { + + // FIXME: Cache the results so we don't have to probe if this media is added a second time + + found_decoder = decoder; + break; } } - // We aren't able to use this Footage - f->set_status(Footage::kInvalid); + if (found_decoder == nullptr) { + // We aren't able to use this Footage + f->set_status(Footage::kInvalid); + f->set_decoder(QString()); + } else { + // We found a Decoder, so we can set this media as valid + f->set_status(Footage::kReady); - return false; + // Attach the successful Decoder to this Footage object + f->set_decoder(found_decoder->id()); + } + + FreeListOfDecoders(decoder_list); + + return (found_decoder != nullptr); +} + +Decoder* olive::CreateDecoderFromID(const QString &id) +{ + if (id.isEmpty()) { + return nullptr; + } + + // Create list to iterate through + QVector decoder_list = ReceiveListOfAllDecoders(); + + Decoder* found_decoder = nullptr; + + foreach (Decoder* d, decoder_list) { + if (d->id() == id) { + found_decoder = d; + break; + } + } + + FreeListOfDecoders(decoder_list, found_decoder); + + return found_decoder; } diff --git a/app/decoder/probeserver.h b/app/decoder/probeserver.h index 5a899d344..bc16eec16 100644 --- a/app/decoder/probeserver.h +++ b/app/decoder/probeserver.h @@ -21,6 +21,7 @@ #ifndef PROBESERVER_H #define PROBESERVER_H +#include "decoder/decoder.h" #include "project/item/footage/footage.h" namespace olive { @@ -46,6 +47,8 @@ namespace olive { */ bool ProbeMedia(Footage* f); +Decoder* CreateDecoderFromID(const QString& id); + } #endif // PROBESERVER_H diff --git a/app/node/node.h b/app/node/node.h index eb14ddf17..1098d8221 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -82,26 +82,6 @@ public: */ virtual QString Description(); - /** - * @brief Add a parameter to this node - * - * The Node takes ownership of this parameter. - * - * This can be either an output or an input at any time. Parameters will always appear in the order they're added. - */ - void AddParameter(NodeParam* param); - - /** - * @brief Signal all dependent Nodes that anything cached between start_range and end_range is now invalid and - * requires re-rendering - * - * Override this if your Node subclass keeps a cache, but call this base function at the end of the subclass function. - * Default behavior is to relay this signal to all connected outputs, which will need to be done as to not break - * the DAG. Even if the time needs to be transformed somehow (e.g. converting media time to sequence time), you can - * call this function with transformed time and relay the signal that way. - */ - virtual void InvalidateCache(const rational& start_range, const rational& end_range); - /** * @brief Return the parameter at a given index */ @@ -134,6 +114,27 @@ public: */ static T* ValueToPtr(const QVariant& ptr); +protected: + /** + * @brief Add a parameter to this node + * + * The Node takes ownership of this parameter. + * + * This can be either an output or an input at any time. Parameters will always appear in the order they're added. + */ + void AddParameter(NodeParam* param); + + /** + * @brief Signal all dependent Nodes that anything cached between start_range and end_range is now invalid and + * requires re-rendering + * + * Override this if your Node subclass keeps a cache, but call this base function at the end of the subclass function. + * Default behavior is to relay this signal to all connected outputs, which will need to be done as to not break + * the DAG. Even if the time needs to be transformed somehow (e.g. converting media time to sequence time), you can + * call this function with transformed time and relay the signal that way. + */ + virtual void InvalidateCache(const rational& start_range, const rational& end_range); + public slots: /** * @brief The main processing function diff --git a/app/project/item/footage/footage.cpp b/app/project/item/footage/footage.cpp index 60eea77a6..574b067cb 100644 --- a/app/project/item/footage/footage.cpp +++ b/app/project/item/footage/footage.cpp @@ -101,6 +101,16 @@ Item::Type Footage::type() const return kFootage; } +const QString &Footage::decoder() +{ + return decoder_; +} + +void Footage::set_decoder(const QString &id) +{ + decoder_ = id; +} + void Footage::ClearStreams() { if (streams_.empty()) { diff --git a/app/project/item/footage/footage.h b/app/project/item/footage/footage.h index 264ed5621..bfe1954d6 100644 --- a/app/project/item/footage/footage.h +++ b/app/project/item/footage/footage.h @@ -185,6 +185,20 @@ public: */ virtual Type type() const override; + /** + * @brief Get the Decoder ID set when this Footage was probed + * + * @return + * + * A decoder ID + */ + const QString& decoder(); + + /** + * @brief Used by decoders when they Probe to attach itself to this Footage + */ + void set_decoder(const QString& id); + private: /** * @brief Internal function to delete all Stream children and empty the array @@ -236,6 +250,11 @@ private: */ Status status_; + /** + * @brief Internal attached decoder ID + */ + QString decoder_; + }; using FootagePtr = std::shared_ptr;