From 94c0914e50087e795d2cd430cf0430c230aeef2e Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 10 Nov 2020 11:23:25 +1100 Subject: [PATCH] codec system rework Codecs are now much cleaner and thread safe by design. --- app/codec/decoder.cpp | 226 +++-- app/codec/decoder.h | 236 +++-- app/codec/ffmpeg/ffmpegcommon.cpp | 14 - app/codec/ffmpeg/ffmpegdecoder.cpp | 1069 ++++++---------------- app/codec/ffmpeg/ffmpegdecoder.h | 207 ++--- app/codec/ffmpeg/ffmpegframepool.cpp | 20 +- app/codec/ffmpeg/ffmpegframepool.h | 4 +- app/codec/frame.cpp | 72 +- app/codec/frame.h | 69 +- app/codec/oiio/CMakeLists.txt | 4 +- app/codec/oiio/oiiocommon.cpp | 102 +++ app/codec/oiio/oiiocommon.h | 47 + app/codec/oiio/oiiodecoder.cpp | 170 +--- app/codec/oiio/oiiodecoder.h | 23 +- app/project/item/footage/audiostream.cpp | 32 - app/project/item/footage/audiostream.h | 11 - app/project/item/footage/footage.cpp | 2 +- 17 files changed, 932 insertions(+), 1376 deletions(-) create mode 100644 app/codec/oiio/oiiocommon.cpp create mode 100644 app/codec/oiio/oiiocommon.h diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index d4dc7108b..e8a5bf71b 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -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/ffmpeg/ffmpegcommon.cpp b/app/codec/ffmpeg/ffmpegcommon.cpp index f66804048..cdc2554e1 100644 --- a/app/codec/ffmpeg/ffmpegcommon.cpp +++ b/app/codec/ffmpeg/ffmpegcommon.cpp @@ -25,9 +25,7 @@ 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 }; @@ -97,14 +95,8 @@ AVPixelFormat FFmpegCommon::GetFFmpegPixelFormat(const PixelFormat::Format &pix_ 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; @@ -116,14 +108,8 @@ AVPixelFormat FFmpegCommon::GetFFmpegPixelFormat(const PixelFormat::Format &pix_ 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: diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index c5a0f3546..9b3ae55cd 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -48,85 +48,47 @@ extern "C" { 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()), + 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_ = FFmpegCommon::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_); + + if (native_pix_fmt_ == PixelFormat::PIX_FMT_INVALID) { + 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,49 +98,57 @@ 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 + FramePtr copy = Frame::Create(); + copy->set_video_params(VideoParams(frame->width, + frame->height, + native_pix_fmt_, + std::static_pointer_cast(stream())->pixel_aspect_ratio(), + std::static_pointer_cast(stream())->interlacing(), + divider)); + copy->set_timestamp(timecode); + copy->allocate(); + + uint8_t* copy_data = reinterpret_cast(copy->data()); + int copy_linesize = copy->linesize_bytes(); + FFmpegFrameToNativeBuffer(frame->data, frame->linesize, ©_data, ©_linesize); + + return copy; } 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 (vs->video_type() == VideoStream::kVideoTypeStill @@ -192,149 +162,38 @@ FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode, const int &divid 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_); + } else { + return_frame = GetFrameFromCache(target_ts); } + // Retrieve frame + 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_, + 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 +201,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 +215,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 +249,131 @@ 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_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; - 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 +435,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 +459,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(), + FFmpegCommon::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 +485,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 +502,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 +519,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 +530,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"; } @@ -807,12 +545,8 @@ bool FFmpegDecoder::ConformAudio(const QAtomicInt *cancelled, const AudioParams PixelFormat::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; - case AV_PIX_FMT_RGB48: - return PixelFormat::PIX_FMT_RGB16U; case AV_PIX_FMT_RGBA64: return PixelFormat::PIX_FMT_RGBA16U; default: @@ -829,116 +563,15 @@ uint64_t FFmpegDecoder::ValidateChannelLayout(AVStream* stream) return av_get_default_channel_layout(stream->codecpar->channels); } -bool FFmpegDecoder::StreamUsesMultipleInstances(StreamPtr stream) +void FFmpegDecoder::FFmpegFrameToNativeBuffer(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,19 +631,15 @@ 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); @@ -1019,16 +648,12 @@ FFmpegFramePool::ElementPtr FFmpegDecoderInstance::RetrieveFrame(const int64_t& 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; } @@ -1036,25 +661,20 @@ FFmpegFramePool::ElementPtr FFmpegDecoderInstance::RetrieveFrame(const int64_t& still_seeking = true; } - 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 +682,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 +698,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 +710,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 + int destination_linesize = Frame::generate_linesize_bytes(VideoParams::GetScaledDimension(instance_.avstream()->codecpar->width, divider), native_pix_fmt_); + uint8_t* destination_data = cached->data(); + FFmpegFrameToNativeBuffer(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 +745,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 +761,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 +774,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 +802,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 +810,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 +818,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 +826,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 +876,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 +896,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 +907,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 +938,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 +950,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 +970,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 +987,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 +1009,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..4dcb5992b 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -41,99 +41,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 +54,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,9 +119,7 @@ private: * * @param error_code */ - void FFmpegError(int error_code); - - void ClearResources(); + static QString FFmpegError(int error_code); void InitScaler(int divider); void FreeScaler(); @@ -203,29 +130,37 @@ private: static uint64_t ValidateChannelLayout(AVStream *stream); - static bool StreamUsesMultipleInstances(StreamPtr stream); + void FFmpegFrameToNativeBuffer(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_; - 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/ffmpegframepool.cpp b/app/codec/ffmpeg/ffmpegframepool.cpp index cca05aed4..ac114ba12 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,11 +28,11 @@ FFmpegFramePool::FFmpegFramePool(int element_count) : MemoryPool(element_count), width_(0), height_(0), - format_(AV_PIX_FMT_NONE) + format_(PixelFormat::PIX_FMT_INVALID) { } -void FFmpegFramePool::SetParameters(int width, int height, AVPixelFormat format) +void FFmpegFramePool::SetParameters(int width, int height, PixelFormat::Format format) { Clear(); @@ -45,17 +43,7 @@ void FFmpegFramePool::SetParameters(int width, int height, AVPixelFormat format) 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_) * height_; } OLIVE_NAMESPACE_EXIT diff --git a/app/codec/ffmpeg/ffmpegframepool.h b/app/codec/ffmpeg/ffmpegframepool.h index 81a31312e..8a72bb61e 100644 --- a/app/codec/ffmpeg/ffmpegframepool.h +++ b/app/codec/ffmpeg/ffmpegframepool.h @@ -32,7 +32,7 @@ class FFmpegFramePool : public MemoryPool public: FFmpegFramePool(int element_count); - void SetParameters(int width, int height, AVPixelFormat format); + void SetParameters(int width, int height, PixelFormat::Format format); const int& width() const { @@ -52,7 +52,7 @@ private: int height_; - AVPixelFormat format_; + PixelFormat::Format format_; }; diff --git a/app/codec/frame.cpp b/app/codec/frame.cpp index 8cd0a53bd..8b06cfbe6 100644 --- a/app/codec/frame.cpp +++ b/app/codec/frame.cpp @@ -45,33 +45,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(params_.width(), params_.format()); + linesize_pixels_ = linesize_ / PixelFormat::BytesPerPixel(params_.format()); } -int Frame::linesize_pixels() const +int Frame::generate_linesize_bytes(int width, PixelFormat::Format format) { - 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 ((PixelFormat::BytesPerPixel(format) * width) + 31) & ~31; } Color Frame::get_pixel(int x, int y) const @@ -80,9 +61,7 @@ Color Frame::get_pixel(int x, int y) const return Color(); } - int pixel_index = y * linesize_pixels() + x; - - int byte_offset = PixelFormat::GetBufferSize(video_params().format(), pixel_index, 1); + int byte_offset = y * linesize_bytes() + x * PixelFormat::BytesPerPixel(video_params().format()); return Color(data_.data() + byte_offset, video_params().format()); } @@ -98,33 +77,11 @@ void Frame::set_pixel(int x, int y, const Color &c) return; } - int pixel_index = y * linesize_pixels() + x; - - int byte_offset = PixelFormat::GetBufferSize(video_params().format(), pixel_index, 1); + int byte_offset = y * linesize_bytes() + x * PixelFormat::BytesPerPixel(video_params().format()); c.toData(data_.data() + byte_offset, video_params().format()); } -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() { // Assume this frame is intended to be a video frame @@ -136,19 +93,4 @@ void Frame::allocate() data_.resize(PixelFormat::GetBufferSize(params_.format(), linesize_, params_.height())); } -bool Frame::is_allocated() const -{ - return !data_.isEmpty(); -} - -void Frame::destroy() -{ - data_.clear(); -} - -int Frame::allocated_size() const -{ - return data_.size(); -} - OLIVE_NAMESPACE_EXIT diff --git a/app/codec/frame.h b/app/codec/frame.h index 7618ad622..57938096d 100644 --- a/app/codec/frame.h +++ b/app/codec/frame.h @@ -47,11 +47,32 @@ 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, PixelFormat::Format format); + + 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(); + } + + PixelFormat::Format format() const + { + return params_.format(); + } Color get_pixel(int x, int y) const; bool contains_pixel(int x, int y) const; @@ -62,18 +83,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 @@ -87,19 +121,28 @@ public: /** * @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(); + } private: VideoParams params_; @@ -110,6 +153,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..4843b25b1 100644 --- a/app/codec/oiio/CMakeLists.txt +++ b/app/codec/oiio/CMakeLists.txt @@ -16,7 +16,9 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} - codec/oiio/oiiodecoder.h + codec/oiio/oiiocommon.cpp + codec/oiio/oiiocommon.h codec/oiio/oiiodecoder.cpp + codec/oiio/oiiodecoder.h PARENT_SCOPE ) diff --git a/app/codec/oiio/oiiocommon.cpp b/app/codec/oiio/oiiocommon.cpp new file mode 100644 index 000000000..f51f82aa2 --- /dev/null +++ b/app/codec/oiio/oiiocommon.cpp @@ -0,0 +1,102 @@ +/*** + + 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 "oiiocommon.h" + +OLIVE_NAMESPACE_ENTER + +void OIIOCommon::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 OIIOCommon::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 OIIOCommon::GetFormatFromOIIOBasetype(const OIIO::ImageSpec& spec) +{ + if (spec.format == OIIO::TypeDesc::UINT8) { + return PixelFormat::PIX_FMT_RGBA8; + } else if (spec.format == OIIO::TypeDesc::UINT16) { + return PixelFormat::PIX_FMT_RGBA16U; + } else if (spec.format == OIIO::TypeDesc::HALF) { + return PixelFormat::PIX_FMT_RGBA16F; + } else if (spec.format == OIIO::TypeDesc::FLOAT) { + return PixelFormat::PIX_FMT_RGBA32F; + } else { + return PixelFormat::PIX_FMT_INVALID; + } +} + +rational OIIOCommon::GetPixelAspectRatioFromOIIO(const OIIO::ImageSpec &spec) +{ + return rational::fromDouble(spec.get_float_attribute("PixelAspectRatio", 1)); +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/codec/oiio/oiiocommon.h b/app/codec/oiio/oiiocommon.h new file mode 100644 index 000000000..aeccc2e0d --- /dev/null +++ b/app/codec/oiio/oiiocommon.h @@ -0,0 +1,47 @@ +/*** + + 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 OIIOCOMMON_H +#define OIIOCOMMON_H + +#include +#include + +#include "codec/frame.h" +#include "render/pixelformat.h" + +OLIVE_NAMESPACE_ENTER + +class OIIOCommon +{ +public: + 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); + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // OIIOCOMMON_H diff --git a/app/codec/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp index 4ddbf0ca5..632220189 100644 --- a/app/codec/oiio/oiiodecoder.cpp +++ b/app/codec/oiio/oiiodecoder.cpp @@ -29,6 +29,7 @@ #include "common/define.h" #include "config/config.h" #include "core.h" +#include "oiiocommon.h" OLIVE_NAMESPACE_ENTER @@ -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,8 @@ 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(OIIOCommon::GetFormatFromOIIOBasetype(in->spec())); + image_stream->set_pixel_aspect_ratio(OIIOCommon::GetPixelAspectRatioFromOIIO(in->spec())); image_stream->set_video_type(VideoStream::kVideoTypeStill); // Images will always have just one stream @@ -95,45 +106,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 +149,14 @@ 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()), + OIIOCommon::GetPixelAspectRatioFromOIIO(buffer_->spec()), VideoParams::kInterlaceNone, // FIXME: Does OIIO deinterlace for us? divider)); frame->allocate(); if (divider == 1) { - BufferToFrame(buffer_, frame); + OIIOCommon::BufferToFrame(buffer_, frame); } else { @@ -161,106 +167,18 @@ FramePtr OIIODecoder::RetrieveVideo(const rational &timecode, const int& divider qWarning() << "OIIO resize failed"; } - BufferToFrame(&dst, frame); + OIIOCommon::BufferToFrame(&dst, frame); } - 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) @@ -299,7 +217,7 @@ bool OIIODecoder::OpenImageHandler(const QString &fn) is_rgba_ = (spec.nchannels == kRGBAChannels); - pix_fmt_ = GetFormatFromOIIOBasetype(spec); + pix_fmt_ = OIIOCommon::GetFormatFromOIIOBasetype(spec); if (pix_fmt_ == PixelFormat::PIX_FMT_INVALID) { qWarning() << "Failed to convert OIIO::ImageDesc to native pixel format"; diff --git a/app/codec/oiio/oiiodecoder.h b/app/codec/oiio/oiiodecoder.h index 11e7638ab..328b54a30 100644 --- a/app/codec/oiio/oiiodecoder.h +++ b/app/codec/oiio/oiiodecoder.h @@ -35,23 +35,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,6 +61,8 @@ private: void CloseImageHandle(); + int64_t last_sequence_index_; + PixelFormat::Format pix_fmt_; bool is_rgba_; diff --git a/app/project/item/footage/audiostream.cpp b/app/project/item/footage/audiostream.cpp index f824c2ec1..946fe8942 100644 --- a/app/project/item/footage/audiostream.cpp +++ b/app/project/item/footage/audiostream.cpp @@ -66,38 +66,6 @@ void AudioStream::set_sample_rate(const int &sample_rate) sample_rate_ = sample_rate; } -bool AudioStream::try_start_conforming(const AudioParams ¶ms) -{ - QMutexLocker locker(proxy_access_lock()); - - if (!currently_conforming_.contains(params) - && !conformed_.contains(params)) { - currently_conforming_.append(params); - return true; - } - - return false; -} - -bool AudioStream::has_conformed_version(const AudioParams ¶ms) -{ - QMutexLocker locker(proxy_access_lock()); - - return conformed_.contains(params); -} - -void AudioStream::append_conformed_version(const AudioParams ¶ms) -{ - { - QMutexLocker locker(proxy_access_lock()); - - currently_conforming_.removeOne(params); - conformed_.append(params); - } - - emit ConformAppended(params); -} - QIcon AudioStream::icon() const { return icon::Audio; diff --git a/app/project/item/footage/audiostream.h b/app/project/item/footage/audiostream.h index f85046839..a4990b385 100644 --- a/app/project/item/footage/audiostream.h +++ b/app/project/item/footage/audiostream.h @@ -49,10 +49,6 @@ public: const int& sample_rate() const; void set_sample_rate(const int& sample_rate); - bool try_start_conforming(const AudioParams& params); - bool has_conformed_version(const AudioParams& params); - void append_conformed_version(const AudioParams& params); - virtual QIcon icon() const override; protected: @@ -60,18 +56,11 @@ protected: virtual void SaveCustomParameters(QXmlStreamWriter* writer) const override; -signals: - void ConformAppended(OLIVE_NAMESPACE::AudioParams params); - private: int channels_; uint64_t layout_; int sample_rate_; - QList conformed_; - - QList currently_conforming_; - }; using AudioStreamPtr = std::shared_ptr; diff --git a/app/project/item/footage/footage.cpp b/app/project/item/footage/footage.cpp index cd92604c6..e30a2d382 100644 --- a/app/project/item/footage/footage.cpp +++ b/app/project/item/footage/footage.cpp @@ -308,7 +308,7 @@ bool Footage::CompareFootageToItsFilename(FootagePtr footage) } else { // Footage may have changed and we'll have to re-probe it. It also may not have, in which // case nothing needs to change. - ItemPtr item = Decoder::ProbeMedia(footage->project(), footage->filename(), nullptr); + ItemPtr item = Decoder::Probe(footage->project(), footage->filename(), nullptr); if (item && item->type() == footage->type()) { // Item is the same type, that's a good sign. Let's look for any differences.