From d9851dc329ba3644dcaea3983161a5065cce4a3c Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 18 Aug 2020 02:29:05 +1000 Subject: [PATCH] decoders: mild refactoring and code cleanup Just making things a little bit nicer. --- app/codec/decoder.cpp | 46 ++++++++ app/codec/decoder.h | 8 +- app/codec/ffmpeg/ffmpegdecoder.cpp | 143 ++++++++++++----------- app/codec/ffmpeg/ffmpegdecoder.h | 9 +- app/codec/oiio/oiiodecoder.cpp | 55 +-------- app/codec/oiio/oiiodecoder.h | 6 - app/project/item/footage/videostream.cpp | 5 + app/project/item/footage/videostream.h | 2 + 8 files changed, 141 insertions(+), 133 deletions(-) diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index 7b5fc8912..973e17e31 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -30,6 +30,7 @@ #include "codec/waveinput.h" #include "codec/waveoutput.h" #include "common/filefunctions.h" +#include "common/timecodefunctions.h" #include "task/taskmanager.h" #include "project/project.h" @@ -220,4 +221,49 @@ void Decoder::SignalProcessingProgress(const int64_t &ts) } } +QString Decoder::TransformImageSequenceFileName(const QString &filename, const int64_t& number) +{ + int digit_count = GetImageSequenceDigitCount(filename); + + QFileInfo file_info(filename); + + QString original_basename = file_info.baseName(); + + QString new_basename = original_basename.left(original_basename.size() - digit_count) + .append(QStringLiteral("%1").arg(number, digit_count, 10, QChar('0'))); + + return file_info.dir().filePath(file_info.fileName().replace(original_basename, new_basename)); +} + +int Decoder::GetImageSequenceDigitCount(const QString &filename) +{ + QString basename = QFileInfo(filename).baseName(); + + // See if basename contains a number at the end + int digit_count = 0; + + for (int i=basename.size()-1;i>=0;i--) { + if (basename.at(i).isDigit()) { + digit_count++; + } else { + break; + } + } + + return digit_count; +} + +int64_t Decoder::GetImageSequenceIndex(const QString &filename) +{ + int digit_count = GetImageSequenceDigitCount(filename); + + QFileInfo file_info(filename); + + QString original_basename = file_info.baseName(); + + QString number_only = original_basename.mid(original_basename.size() - digit_count); + + return number_only.toLongLong(); +} + OLIVE_NAMESPACE_EXIT diff --git a/app/codec/decoder.h b/app/codec/decoder.h index 60c1b6e90..b7cf27472 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -249,9 +249,13 @@ protected: QString GetIndexFilename(); - bool open_; + static QString TransformImageSequenceFileName(const QString& filename, const int64_t& number); - QMutex mutex_; + static int GetImageSequenceDigitCount(const QString& filename); + + static int64_t GetImageSequenceIndex(const QString& filename); + + bool open_; private: StreamPtr stream_; diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index b5c64f81c..297065a08 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -69,8 +69,6 @@ FFmpegDecoder::~FFmpegDecoder() bool FFmpegDecoder::Open() { - QMutexLocker locker(&mutex_); - if (open_) { return true; } @@ -92,24 +90,6 @@ bool FFmpegDecoder::Open() src_pix_fmt_ = static_cast(our_instance->stream()->codecpar->format); ideal_pix_fmt_ = FFmpegCommon::GetCompatiblePixelFormat(src_pix_fmt_); - if (stream()->type() == Stream::kVideo) { - QMutexLocker map_locker(&instance_map_lock_); - - FFmpegFramePool* frame_pool = frame_pool_map_.value(stream().get()); - - if (!frame_pool) { - // 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? - frame_pool = new FFmpegFramePool(32, - our_instance->stream()->codecpar->width, - our_instance->stream()->codecpar->height, - static_cast(our_instance->stream()->codecpar->format)); - frame_pool_map_.insert(stream().get(), frame_pool); - } - - our_instance->SetFramePool(frame_pool); - } - // Determine which Olive native pixel format we retrieved // Note that FFmpeg doesn't support float formats native_pix_fmt_ = GetNativePixelFormat(ideal_pix_fmt_); @@ -117,27 +97,77 @@ bool FFmpegDecoder::Open() Q_ASSERT(native_pix_fmt_ != PixelFormat::PIX_FMT_INVALID); } - time_base_ = our_instance->stream()->time_base; - start_time_ = our_instance->stream()->start_time; + if (StreamUsesMultipleInstances(stream())) { + // Video optimizes with multiple instances that we can swap between + QMutexLocker map_locker(&instance_map_lock_); + + FFmpegFramePool* frame_pool = frame_pool_map_.value(stream().get()); + + if (!frame_pool) { + // 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? + frame_pool = new FFmpegFramePool(64, + our_instance->stream()->codecpar->width, + our_instance->stream()->codecpar->height, + static_cast(our_instance->stream()->codecpar->format)); + frame_pool_map_.insert(stream().get(), frame_pool); + } + + our_instance->SetFramePool(frame_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; - { - QMutexLocker l(&instance_map_lock_); + return true; +} - QList list = instance_map_.value(stream().get()); - list.append(our_instance); - instance_map_.insert(stream().get(), list); +FramePtr FFmpegDecoder::RetrieveStillImage(const rational &timecode, const int ÷r) +{ + // This is a still image + ImageStreamPtr is = std::static_pointer_cast(stream()); + + QString img_filename = stream()->footage()->filename(); + + // If it's an image sequence, we'll probably need to transform the filename + if (stream()->type() == Stream::kVideo) { + int64_t ts = std::static_pointer_cast(stream())->get_time_in_timebase_units(timecode); + + img_filename = TransformImageSequenceFileName(stream()->footage()->filename(), ts); } - return true; + FFmpegDecoderInstance i(img_filename.toUtf8(), stream()->index()); + + AVPacket* pkt = av_packet_alloc(); + AVFrame* frame = av_frame_alloc(); + FramePtr output_frame = nullptr; + + int ret = i.GetFrame(pkt, frame); + + if (ret >= 0) { + output_frame = BuffersToNativeFrame(divider, + is->width(), + is->height(), + 0, + frame->data, + frame->linesize); + } else { + qWarning() << "Failed to retrieve still image from decoder"; + } + + av_frame_free(&frame); + av_packet_free(&pkt); + + return output_frame; } FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode, const int ÷r) { - QMutexLocker locker(&mutex_); - if (!open_) { qWarning() << "Tried to retrieve video on a decoder that's still closed"; return nullptr; @@ -149,41 +179,19 @@ FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode, const int &divid ImageStreamPtr is = std::static_pointer_cast(stream()); - if (stream()->type() == Stream::kImage) { + if (stream()->type() == Stream::kImage + || std::static_pointer_cast(stream())->is_image_sequence()) { - // FIXME: Hacky - FFmpegDecoderInstance i(stream()->footage()->filename().toUtf8(), stream()->index()); - - AVPacket* pkt = av_packet_alloc(); - AVFrame* frame = av_frame_alloc(); - FramePtr output_frame = nullptr; - - int ret = i.GetFrame(pkt, frame); - - if (ret >= 0) { - output_frame = BuffersToNativeFrame(divider, - is->width(), - is->height(), - 0, - frame->data, - frame->linesize); - } else { - qWarning() << "Failed to retrieve still image from decoder"; - } - - av_frame_free(&frame); - av_packet_free(&pkt); - - return output_frame; + return RetrieveStillImage(timecode, divider); } else { FFmpegFramePool::ElementPtr return_frame = nullptr; - int64_t target_ts = Timecode::time_to_timestamp(timecode, time_base_) + start_time_; - VideoStreamPtr vs = std::static_pointer_cast(stream()); + int64_t target_ts = vs->get_time_in_timebase_units(timecode); + FFmpegDecoderInstance* working_instance = nullptr; // Find instance @@ -309,7 +317,7 @@ FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode, const int &divid return BuffersToNativeFrame(divider, vs->width(), vs->height(), - target_ts, + timecode, input_data, input_linesize); } @@ -321,8 +329,6 @@ FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode, const int &divid SampleBufferPtr FFmpegDecoder::RetrieveAudio(const rational &timecode, const rational &length, const AudioParams ¶ms) { - QMutexLocker locker(&mutex_); - if (!open_) { qWarning() << "Tried to retrieve audio on a decoder that's still closed"; return nullptr; @@ -355,9 +361,7 @@ SampleBufferPtr FFmpegDecoder::RetrieveAudio(const rational &timecode, const rat void FFmpegDecoder::Close() { - QMutexLocker locker(&mutex_); - - { + 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_); @@ -776,7 +780,13 @@ uint64_t FFmpegDecoder::ValidateChannelLayout(AVStream* stream) return av_get_default_channel_layout(stream->codecpar->channels); } -FramePtr FFmpegDecoder::BuffersToNativeFrame(int divider, int width, int height, int64_t ts, uint8_t** input_data, int* input_linesize) +bool FFmpegDecoder::StreamUsesMultipleInstances(StreamPtr stream) +{ + return stream->type() == Stream::kVideo + && !std::static_pointer_cast(stream)->is_image_sequence(); +} + +FramePtr FFmpegDecoder::BuffersToNativeFrame(int divider, int width, int height, const rational& ts, uint8_t** input_data, int* input_linesize) { if (divider != scale_divider_) { FreeScaler(); @@ -791,7 +801,7 @@ FramePtr FFmpegDecoder::BuffersToNativeFrame(int divider, int width, int height, std::static_pointer_cast(stream())->pixel_aspect_ratio(), std::static_pointer_cast(stream())->interlacing(), divider)); - copy->set_timestamp(Timecode::timestamp_to_time(ts, time_base_)); + copy->set_timestamp(ts); copy->allocate(); // Convert frame to RGB/A for the rest of the pipeline @@ -1062,8 +1072,7 @@ FFmpegFramePool::ElementPtr FFmpegDecoderInstance::RetrieveFrame(const int64_t& } // Clear early frames - // FIXME: Hardcoded value (only stores a maximum of 2 seconds in the cache at any time) - TruncateCacheRangeTo(2*second_ts_); + TruncateCacheRangeTo(second_ts_); // Append this frame and signal to other threads that a new frame has arrived cached_frames_.append(cached); diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index 9922cac91..b421e453d 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -177,13 +177,17 @@ private: void InitScaler(int divider); void FreeScaler(); + FramePtr RetrieveStillImage(const rational& timecode, const int& divider); + static int GetScaledDimension(int dim, int divider); static PixelFormat::Format GetNativePixelFormat(AVPixelFormat pix_fmt); static uint64_t ValidateChannelLayout(AVStream *stream); - FramePtr BuffersToNativeFrame(int divider, int width, int height, int64_t ts, uint8_t **input_data, int* input_linesize); + static bool StreamUsesMultipleInstances(StreamPtr stream); + + FramePtr BuffersToNativeFrame(int divider, int width, int height, const rational &ts, uint8_t **input_data, int* input_linesize); SwsContext* scale_ctx_; int scale_divider_; @@ -191,9 +195,6 @@ private: AVPixelFormat ideal_pix_fmt_; PixelFormat::Format native_pix_fmt_; - rational time_base_; - int64_t start_time_; - static QHash< Stream*, QList > instance_map_; static QHash< Stream*, FFmpegFramePool* > frame_pool_map_; static QMutex instance_map_lock_; diff --git a/app/codec/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp index 11e73ce03..8946ad6b6 100644 --- a/app/codec/oiio/oiiodecoder.cpp +++ b/app/codec/oiio/oiiodecoder.cpp @@ -143,8 +143,6 @@ bool OIIODecoder::Probe(Footage *f, const QAtomicInt *cancelled) bool OIIODecoder::Open() { - QMutexLocker locker(&mutex_); - Q_ASSERT(stream()); if (stream()->type() != Stream::kVideo && !OpenImageHandler(stream()->footage()->filename())) { @@ -158,16 +156,12 @@ bool OIIODecoder::Open() FramePtr OIIODecoder::RetrieveVideo(const rational &timecode, const int& divider) { - QMutexLocker locker(&mutex_); - if (!open_) { return nullptr; } if (stream()->type() == Stream::kVideo) { - int64_t ts = Timecode::time_to_timestamp(timecode, stream()->timebase()); - - ts += static_cast(stream().get())->start_time(); + int64_t ts = std::static_pointer_cast(stream())->get_time_in_timebase_units(timecode); if (!OpenImageHandler(TransformImageSequenceFileName(stream()->footage()->filename(), ts))) { return nullptr; @@ -210,8 +204,6 @@ FramePtr OIIODecoder::RetrieveVideo(const rational &timecode, const int& divider void OIIODecoder::Close() { - QMutexLocker locker(&mutex_); - CloseImageHandle(); } @@ -324,51 +316,6 @@ bool OIIODecoder::FileTypeIsSupported(const QString& fn) return true; } -int OIIODecoder::GetImageSequenceDigitCount(const QString &filename) -{ - QString basename = QFileInfo(filename).baseName(); - - // See if basename contains a number at the end - int digit_count = 0; - - for (int i=basename.size()-1;i>=0;i--) { - if (basename.at(i).isDigit()) { - digit_count++; - } else { - break; - } - } - - return digit_count; -} - -QString OIIODecoder::TransformImageSequenceFileName(const QString &filename, const int64_t& number) -{ - int digit_count = GetImageSequenceDigitCount(filename); - - QFileInfo file_info(filename); - - QString original_basename = file_info.baseName(); - - QString new_basename = original_basename.left(original_basename.size() - digit_count) - .append(QStringLiteral("%1").arg(number, digit_count, 10, QChar('0'))); - - return file_info.dir().filePath(file_info.fileName().replace(original_basename, new_basename)); -} - -int64_t OIIODecoder::GetImageSequenceIndex(const QString &filename) -{ - int digit_count = GetImageSequenceDigitCount(filename); - - QFileInfo file_info(filename); - - QString original_basename = file_info.baseName(); - - QString number_only = original_basename.mid(original_basename.size() - digit_count); - - return number_only.toLongLong(); -} - bool OIIODecoder::OpenImageHandler(const QString &fn) { image_ = OIIO::ImageInput::open(fn.toStdString()); diff --git a/app/codec/oiio/oiiodecoder.h b/app/codec/oiio/oiiodecoder.h index 1b0a052c0..ff278fa63 100644 --- a/app/codec/oiio/oiiodecoder.h +++ b/app/codec/oiio/oiiodecoder.h @@ -68,12 +68,6 @@ private: static bool FileTypeIsSupported(const QString& fn); - static int GetImageSequenceDigitCount(const QString& filename); - - static QString TransformImageSequenceFileName(const QString& filename, const int64_t& number); - - static int64_t GetImageSequenceIndex(const QString& filename); - bool OpenImageHandler(const QString& fn); void CloseImageHandle(); diff --git a/app/project/item/footage/videostream.cpp b/app/project/item/footage/videostream.cpp index 293a91900..62dbaff4c 100644 --- a/app/project/item/footage/videostream.cpp +++ b/app/project/item/footage/videostream.cpp @@ -71,6 +71,11 @@ void VideoStream::set_image_sequence(bool e) is_image_sequence_ = e; } +int64_t VideoStream::get_time_in_timebase_units(const rational &time) const +{ + return Timecode::time_to_timestamp(time, timebase()) + start_time(); +} + /* int64_t VideoStream::get_closest_timestamp_in_frame_index(const rational &time) { diff --git a/app/project/item/footage/videostream.h b/app/project/item/footage/videostream.h index f8a81b898..6140f7c15 100644 --- a/app/project/item/footage/videostream.h +++ b/app/project/item/footage/videostream.h @@ -47,6 +47,8 @@ public: bool is_image_sequence() const; void set_image_sequence(bool e); + int64_t get_time_in_timebase_units(const rational& time) const; + /* int64_t get_closest_timestamp_in_frame_index(const rational& time); int64_t get_closest_timestamp_in_frame_index(int64_t timestamp);