From e4c3b6bf7bce5c269f1843c9b69099939d360545 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sun, 3 May 2020 16:07:10 +1000 Subject: [PATCH] renderer/decoder: simplified audio PCM transcode Turned the two-step PCM transcode into one step and simplified/removed much of the unnecessary infrastructure that supported it. This makes the code cleaner and generally improves the code paths. --- app/codec/decoder.cpp | 22 +- app/codec/decoder.h | 30 +- app/codec/ffmpeg/ffmpegdecoder.cpp | 355 +++++++++++----------- app/codec/ffmpeg/ffmpegdecoder.h | 9 +- app/codec/oiio/oiiodecoder.cpp | 11 - app/codec/oiio/oiiodecoder.h | 1 - app/core.cpp | 12 +- app/project/item/footage/audiostream.cpp | 69 +---- app/project/item/footage/audiostream.h | 16 +- app/project/item/footage/stream.cpp | 9 +- app/project/item/footage/stream.h | 7 +- app/project/item/footage/videostream.cpp | 4 +- app/render/backend/CMakeLists.txt | 2 - app/render/backend/audiorenderbackend.cpp | 52 +++- app/render/backend/audiorenderbackend.h | 6 +- app/render/backend/indexmanager.cpp | 119 -------- app/render/backend/indexmanager.h | 82 ----- app/render/backend/renderbackend.cpp | 81 ----- app/render/backend/renderbackend.h | 5 - app/render/backend/renderworker.cpp | 12 +- app/render/backend/renderworker.h | 4 - app/render/backend/videorenderworker.cpp | 8 - app/render/backend/videorenderworker.h | 2 - app/task/CMakeLists.txt | 1 - app/task/conform/conform.cpp | 2 +- app/task/index/CMakeLists.txt | 22 -- app/task/index/index.cpp | 51 ---- app/task/index/index.h | 44 --- 28 files changed, 275 insertions(+), 763 deletions(-) delete mode 100644 app/render/backend/indexmanager.cpp delete mode 100644 app/render/backend/indexmanager.h delete mode 100644 app/task/index/CMakeLists.txt delete mode 100644 app/task/index/index.cpp delete mode 100644 app/task/index/index.h diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index e090238b1..239822742 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -29,8 +29,6 @@ #include "codec/oiio/oiiodecoder.h" #include "codec/waveinput.h" #include "codec/waveoutput.h" -#include "render/backend/indexmanager.h" -#include "task/index/index.h" #include "task/taskmanager.h" OLIVE_NAMESPACE_ENTER @@ -134,16 +132,6 @@ bool Decoder::ProbeMedia(Footage *f, const QAtomicInt* cancelled) // FIXME: Cache the results so we don't have to probe if this media is added a second time - // Start an index task - foreach (StreamPtr stream, f->streams()) { - if (stream->type() == Stream::kAudio) { - QMetaObject::invokeMethod(IndexManager::instance(), - "StartIndexingStream", - Qt::QueuedConnection, - OLIVE_NS_ARG(StreamPtr, stream)); - } - } - return true; } } @@ -173,7 +161,7 @@ DecoderPtr Decoder::CreateFromID(const QString &id) return nullptr; } -void Decoder::Conform(const AudioRenderingParams ¶ms, const QAtomicInt* cancelled) +/*void Decoder::Conform(const AudioRenderingParams ¶ms, const QAtomicInt* cancelled) { if (stream()->type() != Stream::kAudio) { // Nothing to be done @@ -265,7 +253,7 @@ void Decoder::Conform(const AudioRenderingParams ¶ms, const QAtomicInt* canc } else { qWarning() << "Failed to conform file:" << stream()->footage()->filename(); } -} +}*/ void Decoder::ConformInternal(SwrContext* resampler, WaveOutput* output, const char* in_data, int in_sample_count) { @@ -319,12 +307,14 @@ QString Decoder::GetConformedFilename(const AudioRenderingParams ¶ms) return index_fn; } -void Decoder::ProxyVideo(const QAtomicInt *, int ) +bool Decoder::ProxyVideo(const QAtomicInt *, int ) { + return false; } -void Decoder::ProxyAudio(const QAtomicInt *) +bool Decoder::ConformAudio(const QAtomicInt *, const AudioRenderingParams& ) { + return false; } bool Decoder::HasConformedVersion(const AudioRenderingParams ¶ms) diff --git a/app/codec/decoder.h b/app/codec/decoder.h index 2a6e1a81d..5ab6b0c9c 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -117,11 +117,6 @@ public: */ virtual bool Open() = 0; - /** - * @brief Determine whether the Decoder is able to retrieve data - */ - virtual RetrieveState GetRetrieveState(const rational& time) = 0; - /** * @brief Retrieve video frame * @@ -216,7 +211,15 @@ public: static DecoderPtr CreateFromID(const QString& id); /** - * @brief AUDIO ONLY: Conform an audio stream to match certain parameters + * @brief VIDEO ONLY: Produce a compressed EXR proxy with the specified divider + */ + virtual bool ProxyVideo(const QAtomicInt* cancelled, int divider); + + /** + * @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 @@ -226,20 +229,7 @@ public: * 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. */ - void Conform(const AudioRenderingParams& params, const QAtomicInt* cancelled); - - /** - * @brief VIDEO ONLY: Produce a compressed EXR proxy with the specified divider - */ - virtual void ProxyVideo(const QAtomicInt* cancelled, int divider); - - /** - * @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. - */ - virtual void ProxyAudio(const QAtomicInt* cancelled); + virtual bool ConformAudio(const QAtomicInt* cancelled, const AudioRenderingParams ¶ms); /** * @brief AUDIO ONLY: Returns whether a transcode of this audio matching the specified params diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index c7438cd37..c8139d0d4 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -108,23 +108,9 @@ bool FFmpegDecoder::Open() // Determine which Olive native pixel format we retrieved // Note that FFmpeg doesn't support float formats - switch (ideal_pix_fmt_) { - case AV_PIX_FMT_RGB24: - native_pix_fmt_ = PixelFormat::PIX_FMT_RGB8; - break; - case AV_PIX_FMT_RGBA: - native_pix_fmt_ = PixelFormat::PIX_FMT_RGBA8; - break; - case AV_PIX_FMT_RGB48: - native_pix_fmt_ = PixelFormat::PIX_FMT_RGB16U; - break; - case AV_PIX_FMT_RGBA64: - native_pix_fmt_ = PixelFormat::PIX_FMT_RGBA16U; - break; - default: - // We should never get here, but just in case... - qFatal("Invalid output format"); - } + native_pix_fmt_ = GetNativePixelFormat(ideal_pix_fmt_); + + Q_ASSERT(native_pix_fmt_ != PixelFormat::PIX_FMT_INVALID); aspect_ratio_ = our_instance->sample_aspect_ratio(); } @@ -146,29 +132,6 @@ bool FFmpegDecoder::Open() return true; } -Decoder::RetrieveState FFmpegDecoder::GetRetrieveState(const rational& time) -{ - QMutexLocker locker(&mutex_); - - if (!open_) { - return kFailedToOpen; - } - - if (stream()->type() == Stream::kVideo) { - - // Do nothing - - } else if (stream()->type() == Stream::kAudio) { - AudioStreamPtr audio_stream = std::static_pointer_cast(stream()); - - if (time > audio_stream->index_length() && !audio_stream->index_done()) { - return kIndexUnavailable; - } - } - - return kReady; -} - FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode, const int ÷r) { QMutexLocker locker(&mutex_); @@ -623,173 +586,174 @@ void FFmpegDecoder::Error(const QString &s) ClearResources(); } -void FFmpegDecoder::ProxyVideo(const QAtomicInt *cancelled, int divider) +bool FFmpegDecoder::ProxyVideo(const QAtomicInt *cancelled, int divider) { - // Iterate through each video frame transcode each frame to compressed EXR - QMutexLocker locker(stream()->index_process_lock()); + return false; - QByteArray fn_bytes = stream()->footage()->filename().toUtf8(); + VideoStreamPtr video_stream = std::static_pointer_cast(stream()); - FFmpegDecoderInstance index_instance(fn_bytes.constData(), stream()->index()); + QString frame_index_file = GetIndexFilename().append('d').append(QString::number(divider)); + if (QFileInfo::exists(frame_index_file)) { -} + // A proxy of this type already exists so we can do nothing + video_stream->append_proxy(divider); -void FFmpegDecoder::ProxyAudio(const QAtomicInt *cancelled) -{ - // Iterate through each audio frame and extract the PCM data - QMutexLocker locker(stream()->index_process_lock()); - - if (QFileInfo::exists(GetIndexFilename())) { - WaveInput input(GetIndexFilename()); - if (input.open()) { - std::static_pointer_cast(stream())->set_index_done(true); - std::static_pointer_cast(stream())->set_index_length(input.params().bytes_to_time(input.data_length())); - - input.close(); - } } else { - QByteArray fn_bytes = stream()->footage()->filename().toUtf8(); - FFmpegDecoderInstance index_instance(fn_bytes.constData(), stream()->index()); + // Iterate each frame and transcode it to EXR + FFmpegDecoderInstance instance(stream()->footage()->filename().toUtf8(), stream()->index()); - uint64_t channel_layout = index_instance.stream()->codecpar->channel_layout; - if (!channel_layout) { - if (!index_instance.stream()->codecpar->channels) { - // No channel data - we can't do anything with this - return; - } - - channel_layout = static_cast(av_get_default_channel_layout(index_instance.stream()->codecpar->channels)); - } - - AudioStreamPtr audio_stream = std::static_pointer_cast(stream()); - - // This should be unnecessary, but just in case... - audio_stream->clear_index(); - - SwrContext* resampler = nullptr; - AVSampleFormat src_sample_fmt = static_cast(index_instance.stream()->codecpar->format); - AVSampleFormat dst_sample_fmt; - - // We don't use planar types internally, so if this is a planar format convert it now - if (av_sample_fmt_is_planar(src_sample_fmt)) { - dst_sample_fmt = av_get_packed_sample_fmt(src_sample_fmt); - - resampler = swr_alloc_set_opts(nullptr, - static_cast(index_instance.stream()->codecpar->channel_layout), - dst_sample_fmt, - index_instance.stream()->codecpar->sample_rate, - static_cast(index_instance.stream()->codecpar->channel_layout), - src_sample_fmt, - index_instance.stream()->codecpar->sample_rate, - 0, - nullptr); - - swr_init(resampler); - } else { - dst_sample_fmt = src_sample_fmt; - } - - AudioRenderingParams wave_params(index_instance.stream()->codecpar->sample_rate, - channel_layout, - FFmpegCommon::GetNativeSampleFormat(dst_sample_fmt)); - WaveOutput wave_out(GetIndexFilename(), wave_params); + int ret; AVPacket* pkt = av_packet_alloc(); AVFrame* frame = av_frame_alloc(); - int ret; - if (wave_out.open()) { - bool success = false; + while (true) { + ret = instance.GetFrame(pkt, frame); - while (true) { - // Check if we have a `cancelled` ptr and its value - if (cancelled && *cancelled) { - break; - } + if (ret < 0) { + if (ret == AVERROR_EOF) { - ret = index_instance.GetFrame(pkt, frame); - - if (ret < 0) { - - if (ret == AVERROR_EOF) { - success = true; - } else { - char err_str[50]; - av_strerror(ret, err_str, 50); - qWarning() << "Failed to index:" << ret << err_str; - } - break; - - } else { - char* data; - int nb_samples; - - if (resampler) { - - nb_samples = swr_get_out_samples(resampler, frame->nb_samples); - - data = new char[wave_params.samples_to_bytes(nb_samples)]; - - // We must need to resample this (mainly just convert from planar to packed if necessary) - nb_samples = swr_convert(resampler, - reinterpret_cast(&data), - nb_samples, - const_cast(frame->data), - frame->nb_samples); - - if (nb_samples < 0) { - char err_str[50]; - av_strerror(nb_samples, err_str, 50); - qWarning() << "libswresample failed with error:" << nb_samples << err_str; - break; - } - - } else { - - // No resampling required, we can write directly from the frame buffer - data = reinterpret_cast(frame->data[0]); - nb_samples = frame->nb_samples; - - } - - // Write packed WAV data to the disk cache - wave_out.write(data, wave_params.samples_to_bytes(nb_samples)); - - audio_stream->set_index_length(wave_params.bytes_to_time(wave_out.data_length())); - - // If we allocated an output for the resampler, delete it here - if (data != reinterpret_cast(frame->data[0])) { - delete [] data; - } - - SignalIndexProgress(frame->pts); } } - - wave_out.close(); - - if (success) { - audio_stream->set_index_done(true); - } else { - // Audio index didn't complete, delete it - QFile(GetIndexFilename()).remove(); - audio_stream->clear_index(); - } - } else { - qWarning() << "Failed to open WAVE output for indexing"; - } - - if (resampler != nullptr) { - swr_free(&resampler); } av_frame_free(&frame); av_packet_free(&pkt); + } } +bool FFmpegDecoder::ConformAudio(const QAtomicInt *cancelled, const AudioRenderingParams &p) +{ + // 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()); + + // Handle NULL channel layout + uint64_t channel_layout = ValidateChannelLayout(index_instance.stream()); + if (!channel_layout) { + qCritical() << "Failed to determine channel layout of audio file, could not conform"; + return false; + } + + // Create resampling context + SwrContext* resampler = swr_alloc_set_opts(nullptr, + p.channel_layout(), + FFmpegCommon::GetFFmpegSampleFormat(p.format()), + p.sample_rate(), + static_cast(index_instance.stream()->codecpar->channel_layout), + static_cast(index_instance.stream()->codecpar->format), + index_instance.stream()->codecpar->sample_rate, + 0, + nullptr); + + swr_init(resampler); + + WaveOutput wave_out(conformed_fn, p); + + AVPacket* pkt = av_packet_alloc(); + AVFrame* frame = av_frame_alloc(); + int ret; + + bool success = false; + + if (wave_out.open()) { + while (true) { + // Check if we have a `cancelled` ptr and its value + if (cancelled && *cancelled) { + break; + } + + ret = index_instance.GetFrame(pkt, frame); + + if (ret < 0) { + + if (ret == AVERROR_EOF) { + success = true; + } else { + char err_str[50]; + av_strerror(ret, err_str, 50); + qWarning() << "Failed to index:" << ret << err_str; + } + break; + + } else { + // Allocate buffers + int nb_samples = swr_get_out_samples(resampler, frame->nb_samples); + char* data = new char[p.samples_to_bytes(nb_samples)]; + + // Resample audio to our destination parameters + nb_samples = swr_convert(resampler, + reinterpret_cast(&data), + nb_samples, + const_cast(frame->data), + frame->nb_samples); + + if (nb_samples < 0) { + char err_str[50]; + av_strerror(nb_samples, err_str, 50); + qWarning() << "libswresample failed with error:" << nb_samples << err_str; + break; + } + + // Write packed WAV data to the disk cache + wave_out.write(data, p.samples_to_bytes(nb_samples)); + + // If we allocated an output for the resampler, delete it here + if (data != reinterpret_cast(frame->data[0])) { + delete [] data; + } + + SignalIndexProgress(frame->pts); + } + } + + 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"; + } + + swr_free(&resampler); + + av_frame_free(&frame); + av_packet_free(&pkt); + + return success; +} + QString FFmpegDecoder::GetIndexFilename() { return FileFunctions::GetMediaIndexFilename(FileFunctions::GetUniqueFileIdentifier(stream()->footage()->filename())) @@ -801,6 +765,31 @@ int FFmpegDecoder::GetScaledDimension(int dim, int divider) return dim / divider; } +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: + return PixelFormat::PIX_FMT_INVALID; + } +} + +uint64_t FFmpegDecoder::ValidateChannelLayout(AVStream* stream) +{ + if (stream->codecpar->channel_layout) { + return stream->codecpar->channel_layout; + } + + return av_get_default_channel_layout(stream->codecpar->channels); +} + int FFmpegDecoderInstance::GetFrame(AVPacket *pkt, AVFrame *frame) { bool eof = false; diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index 44d102520..4b34cce8d 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -134,7 +134,6 @@ public: virtual bool Probe(Footage *f, const QAtomicInt *cancelled) override; virtual bool Open() override; - virtual RetrieveState GetRetrieveState(const rational &time) override; virtual FramePtr RetrieveVideo(const rational &timecode, const int& divider) override; virtual SampleBufferPtr RetrieveAudio(const rational &timecode, const rational &length, const AudioRenderingParams& params) override; virtual void Close() override; @@ -144,8 +143,8 @@ public: virtual bool SupportsVideo() override; virtual bool SupportsAudio() override; - virtual void ProxyVideo(const QAtomicInt* cancelled, int divider) override; - virtual void ProxyAudio(const QAtomicInt* cancelled) override; + virtual bool ProxyVideo(const QAtomicInt* cancelled, int divider) override; + virtual bool ConformAudio(const QAtomicInt* cancelled, const AudioRenderingParams& p) override; private: /** @@ -176,6 +175,10 @@ private: static int GetScaledDimension(int dim, int divider); + static PixelFormat::Format GetNativePixelFormat(AVPixelFormat pix_fmt); + + static uint64_t ValidateChannelLayout(AVStream *stream); + SwsContext* scale_ctx_; int scale_divider_; AVPixelFormat src_pix_fmt_; diff --git a/app/codec/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp index e55e379ee..1083cab30 100644 --- a/app/codec/oiio/oiiodecoder.cpp +++ b/app/codec/oiio/oiiodecoder.cpp @@ -154,17 +154,6 @@ bool OIIODecoder::Open() return true; } -Decoder::RetrieveState OIIODecoder::GetRetrieveState(const rational &time) -{ - QMutexLocker locker(&mutex_); - - if (!open_) { - return kFailedToOpen; - } - - return kReady; -} - FramePtr OIIODecoder::RetrieveVideo(const rational &timecode, const int& divider) { QMutexLocker locker(&mutex_); diff --git a/app/codec/oiio/oiiodecoder.h b/app/codec/oiio/oiiodecoder.h index b56a49b46..3d265f1a9 100644 --- a/app/codec/oiio/oiiodecoder.h +++ b/app/codec/oiio/oiiodecoder.h @@ -40,7 +40,6 @@ public: virtual bool Probe(Footage *f, const QAtomicInt* cancelled) override; virtual bool Open() override; - virtual RetrieveState GetRetrieveState(const rational &time) override; virtual FramePtr RetrieveVideo(const rational &timecode, const int& divider) override; virtual void Close() override; diff --git a/app/core.cpp b/app/core.cpp index 93dbc4585..25123d675 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -48,7 +48,6 @@ #include "project/projectimportmanager.h" #include "project/projectloadmanager.h" #include "project/projectsavemanager.h" -#include "render/backend/indexmanager.h" #include "render/backend/opengl/opengltexturecache.h" #include "render/colormanager.h" #include "render/diskmanager.h" @@ -119,15 +118,9 @@ bool Core::Start() // Set up node factory/library NodeFactory::Initialize(); - // Set up the index manager for renderers - IndexManager::CreateInstance(); - // Set up color manager's default config ColorManager::SetUpDefaultConfig(); - // Initialize disk service - DiskManager::CreateInstance(); - // Initialize task manager TaskManager::CreateInstance(); @@ -226,8 +219,6 @@ void Core::Stop() NodeFactory::Destroy(); - IndexManager::DestroyInstance(); - delete main_window_; } @@ -565,6 +556,9 @@ void Core::StartGUI(bool full_screen) // Initialize audio service AudioManager::CreateInstance(); + // Initialize disk service + DiskManager::CreateInstance(); + // Initialize pixel service PixelFormat::CreateInstance(); diff --git a/app/project/item/footage/audiostream.cpp b/app/project/item/footage/audiostream.cpp index 6fb93267a..b901d488a 100644 --- a/app/project/item/footage/audiostream.cpp +++ b/app/project/item/footage/audiostream.cpp @@ -22,8 +22,7 @@ OLIVE_NAMESPACE_ENTER -AudioStream::AudioStream() : - index_done_(false) +AudioStream::AudioStream() { set_type(kAudio); } @@ -65,68 +64,32 @@ void AudioStream::set_sample_rate(const int &sample_rate) sample_rate_ = sample_rate; } -const rational &AudioStream::index_length() +bool AudioStream::try_start_conforming(const AudioRenderingParams ¶ms) { - QMutexLocker locker(index_access_lock()); + QMutexLocker locker(proxy_access_lock()); - return index_length_; -} - -void AudioStream::set_index_length(const rational &index_length) -{ - { - QMutexLocker locker(index_access_lock()); - - index_length_ = index_length; - } - - emit IndexChanged(); -} - -const bool &AudioStream::index_done() -{ - QMutexLocker locker(index_access_lock()); - - return index_done_; -} - -void AudioStream::set_index_done(const bool& index_done) -{ - { - QMutexLocker locker(index_access_lock()); - - index_done_ = index_done; - } - - emit IndexChanged(); -} - -void AudioStream::clear_index() -{ - QMutexLocker locker(index_access_lock()); - - index_done_ = false; - index_length_ = 0; -} - -bool AudioStream::has_conformed_version(const AudioRenderingParams ¶ms) -{ - QMutexLocker locker(index_access_lock()); - - foreach (const AudioRenderingParams& p, conformed_) { - if (p == params) { - return true; - } + if (!currently_conforming_.contains(params) + && !conformed_.contains(params)) { + currently_conforming_.append(params); + return true; } return false; } +bool AudioStream::has_conformed_version(const AudioRenderingParams ¶ms) +{ + QMutexLocker locker(proxy_access_lock()); + + return conformed_.contains(params); +} + void AudioStream::append_conformed_version(const AudioRenderingParams ¶ms) { { - QMutexLocker locker(index_access_lock()); + QMutexLocker locker(proxy_access_lock()); + currently_conforming_.removeOne(params); conformed_.append(params); } diff --git a/app/project/item/footage/audiostream.h b/app/project/item/footage/audiostream.h index b3a432309..ebe605fa8 100644 --- a/app/project/item/footage/audiostream.h +++ b/app/project/item/footage/audiostream.h @@ -49,29 +49,21 @@ public: const int& sample_rate() const; void set_sample_rate(const int& sample_rate); - const rational& index_length(); - void set_index_length(const rational& index_length); - - const bool& index_done(); - void set_index_done(const bool &index_done); - - void clear_index(); - + bool try_start_conforming(const AudioRenderingParams& params); bool has_conformed_version(const AudioRenderingParams& params); void append_conformed_version(const AudioRenderingParams& params); signals: - void ConformAppended(const AudioRenderingParams& params); + void ConformAppended(OLIVE_NAMESPACE::AudioRenderingParams params); private: int channels_; uint64_t layout_; int sample_rate_; - rational index_length_; - bool index_done_; + QList conformed_; - QVector conformed_; + QList currently_conforming_; }; diff --git a/app/project/item/footage/stream.cpp b/app/project/item/footage/stream.cpp index 6ee029a71..71f0b7993 100644 --- a/app/project/item/footage/stream.cpp +++ b/app/project/item/footage/stream.cpp @@ -139,14 +139,9 @@ QIcon Stream::IconFromType(const Stream::Type &type) return QIcon(); } -QMutex* Stream::index_process_lock() +QMutex *Stream::proxy_access_lock() { - return &index_process_lock_; -} - -QMutex *Stream::index_access_lock() -{ - return &index_access_lock_; + return &proxy_access_lock_; } void Stream::FootageSetEvent(Footage*) diff --git a/app/project/item/footage/stream.h b/app/project/item/footage/stream.h index 663b15eba..62e942207 100644 --- a/app/project/item/footage/stream.h +++ b/app/project/item/footage/stream.h @@ -92,8 +92,7 @@ public: static QIcon IconFromType(const Type& type); - QMutex* index_process_lock(); - QMutex* index_access_lock(); + QMutex* proxy_access_lock(); protected: virtual void FootageSetEvent(Footage*); @@ -120,9 +119,7 @@ private: bool enabled_; - QMutex index_process_lock_; - - QMutex index_access_lock_; + QMutex proxy_access_lock_; }; diff --git a/app/project/item/footage/videostream.cpp b/app/project/item/footage/videostream.cpp index a20899362..0a90fd46a 100644 --- a/app/project/item/footage/videostream.cpp +++ b/app/project/item/footage/videostream.cpp @@ -75,14 +75,14 @@ void VideoStream::set_image_sequence(bool e) bool VideoStream::has_proxy(const int ÷r) { - QMutexLocker locker(index_access_lock()); + QMutexLocker locker(proxy_access_lock()); return proxies_.contains(divider); } void VideoStream::append_proxy(const int ÷r) { - QMutexLocker locker(index_access_lock()); + QMutexLocker locker(proxy_access_lock()); proxies_.append(divider); } diff --git a/app/render/backend/CMakeLists.txt b/app/render/backend/CMakeLists.txt index 515f1b1e9..d191f51a0 100644 --- a/app/render/backend/CMakeLists.txt +++ b/app/render/backend/CMakeLists.txt @@ -34,8 +34,6 @@ set(OLIVE_SOURCES render/backend/audiorenderbackend.cpp render/backend/audiorenderworker.h render/backend/audiorenderworker.cpp - render/backend/indexmanager.h - render/backend/indexmanager.cpp render/backend/videorenderbackend.h render/backend/videorenderbackend.cpp diff --git a/app/render/backend/audiorenderbackend.cpp b/app/render/backend/audiorenderbackend.cpp index b4df5f4ba..cb48ebb4d 100644 --- a/app/render/backend/audiorenderbackend.cpp +++ b/app/render/backend/audiorenderbackend.cpp @@ -25,7 +25,8 @@ #include "audiorenderworker.h" #include "common/filefunctions.h" -#include "render/backend/indexmanager.h" +#include "task/conform/conform.h" +#include "task/taskmanager.h" OLIVE_NAMESPACE_ENTER @@ -33,7 +34,6 @@ AudioRenderBackend::AudioRenderBackend(QObject *parent) : RenderBackend(parent), ic_from_conform_(false) { - connect(IndexManager::instance(), &IndexManager::StreamConformAppended, this, &AudioRenderBackend::ConformUpdated); connect(this, &AudioRenderBackend::QueueComplete, this, &AudioRenderBackend::FilterQueueCompleteSignal); } @@ -154,6 +154,30 @@ void AudioRenderBackend::InvalidateCacheInternal(const rational &start_range, co RenderBackend::InvalidateCacheInternal(start_range, end_range); } +void AudioRenderBackend::ListenForConformSignal(AudioStreamPtr s) +{ + foreach (const ConformWaitInfo& info, conform_wait_info_) { + if (info.stream == s) { + // We've probably already connected to this one + return; + } + } + + connect(s.get(), &AudioStream::ConformAppended, this, &AudioRenderBackend::ConformUpdated); +} + +void AudioRenderBackend::StopListeningForConformSignal(AudioStream* s) +{ + foreach (const ConformWaitInfo& info, conform_wait_info_) { + if (info.stream.get() == s) { + // There are still conforms we're waiting for, don't disconnect + return; + } + } + + disconnect(s, &AudioStream::ConformAppended, this, &AudioRenderBackend::ConformUpdated); +} + void AudioRenderBackend::ConformUnavailable(StreamPtr stream, TimeRange range, rational stream_time, AudioRenderingParams params) { ConformWaitInfo info = {stream, params, range, stream_time}; @@ -164,26 +188,38 @@ void AudioRenderBackend::ConformUnavailable(StreamPtr stream, TimeRange range, r AudioStreamPtr audio_stream = std::static_pointer_cast(stream); - if (IndexManager::instance()->IsConforming(audio_stream, params)) { + if (audio_stream->try_start_conforming(params)) { + + // Start indexing process + ListenForConformSignal(audio_stream); conform_wait_info_.append(info); + ConformTask* conform_task = new ConformTask(audio_stream, params); + + TaskManager::instance()->AddTask(conform_task); + } else if (audio_stream->has_conformed_version(params)) { - // Index JUST finished, requeue this time + // Conform JUST finished, requeue this time + ic_from_conform_ = true; InvalidateCache(range, nullptr); + ic_from_conform_ = false; } else { - // Start indexing process + // A conform task is already running, so we'll just wait for it + ListenForConformSignal(audio_stream); + conform_wait_info_.append(info); - IndexManager::instance()->StartConformingStream(audio_stream, params); } } -void AudioRenderBackend::ConformUpdated(Stream *stream, AudioRenderingParams params) +void AudioRenderBackend::ConformUpdated(AudioRenderingParams params) { + AudioStream *stream = static_cast(sender()); + for (int i=0;i conform_wait_info_; AudioRenderingParams params_; @@ -88,7 +92,7 @@ private: private slots: void ConformUnavailable(StreamPtr stream, TimeRange range, rational stream_time, AudioRenderingParams params); - void ConformUpdated(Stream *stream, AudioRenderingParams params); + void ConformUpdated(OLIVE_NAMESPACE::AudioRenderingParams params); void TruncateCache(const rational& r); diff --git a/app/render/backend/indexmanager.cpp b/app/render/backend/indexmanager.cpp deleted file mode 100644 index 2cce2dc93..000000000 --- a/app/render/backend/indexmanager.cpp +++ /dev/null @@ -1,119 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#include "indexmanager.h" - -#include "task/taskmanager.h" - -OLIVE_NAMESPACE_ENTER - -IndexManager* IndexManager::instance_ = nullptr; - -void IndexManager::CreateInstance() -{ - instance_ = new IndexManager(); -} - -IndexManager *IndexManager::instance() -{ - return instance_; -} - -void IndexManager::DestroyInstance() -{ - delete instance_; - instance_ = nullptr; -} - -void IndexManager::StartIndexingStream(StreamPtr stream) -{ - if (IsIndexing(stream)) { - return; - } - - IndexTask* index_task = new IndexTask(stream); - indexing_.append({stream, index_task}); - - connect(stream.get(), &Stream::IndexChanged, this, &IndexManager::StreamIndexUpdatedEvent, Qt::QueuedConnection); - connect(index_task, &IndexTask::Succeeded, this, &IndexManager::IndexTaskFinished, Qt::QueuedConnection); - - TaskManager::instance()->AddTask(index_task); -} - -void IndexManager::StartConformingStream(AudioStreamPtr stream, AudioRenderingParams params) -{ - if (IsConforming(stream, params)) { - return; - } - - ConformTask* conform_task = new ConformTask(stream, params); - conforming_.append({stream, params, conform_task}); - - connect(stream.get(), &AudioStream::ConformAppended, this, &IndexManager::StreamConformAppendedEvent, Qt::QueuedConnection); - connect(conform_task, &ConformTask::Succeeded, this, &IndexManager::IndexTaskFinished, Qt::QueuedConnection); - - TaskManager::instance()->AddTask(conform_task); -} - -bool IndexManager::IsIndexing(StreamPtr stream) const -{ - foreach (const IndexPair& stp, indexing_) { - if (stp.stream == stream) { - return true; - } - } - - return false; -} - -bool IndexManager::IsConforming(AudioStreamPtr stream, const AudioRenderingParams ¶ms) const -{ - foreach (const ConformPair& cfp, conforming_) { - if (cfp.stream == stream && cfp.params == params) { - return true; - } - } - - return false; -} - -void IndexManager::IndexTaskFinished() -{ - for (int i=0;i(sender())); -} - -void IndexManager::StreamConformAppendedEvent(const AudioRenderingParams ¶ms) -{ - emit StreamConformAppended(static_cast(sender()), params); -} - -OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/indexmanager.h b/app/render/backend/indexmanager.h deleted file mode 100644 index cbebe4c7d..000000000 --- a/app/render/backend/indexmanager.h +++ /dev/null @@ -1,82 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#ifndef INDEXMANAGER_H -#define INDEXMANAGER_H - -#include - -#include "project/item/footage/stream.h" -#include "task/conform/conform.h" -#include "task/index/index.h" - -OLIVE_NAMESPACE_ENTER - -class IndexManager : public QObject -{ - Q_OBJECT -public: - IndexManager() = default; - - static void CreateInstance(); - static IndexManager* instance(); - static void DestroyInstance(); - - bool IsIndexing(StreamPtr stream) const; - bool IsConforming(AudioStreamPtr stream, const AudioRenderingParams& params) const; - -public slots: - void StartIndexingStream(OLIVE_NAMESPACE::StreamPtr stream); - void StartConformingStream(OLIVE_NAMESPACE::AudioStreamPtr stream, OLIVE_NAMESPACE::AudioRenderingParams params); - -signals: - void StreamIndexUpdated(Stream* stream); - void StreamConformAppended(Stream* stream, OLIVE_NAMESPACE::AudioRenderingParams params); - -private: - static IndexManager* instance_; - - struct IndexPair { - StreamPtr stream; - IndexTask* task; - }; - - struct ConformPair { - StreamPtr stream; - AudioRenderingParams params; - ConformTask* task; - }; - - QList indexing_; - - QList conforming_; - -private slots: - void IndexTaskFinished(); - - void StreamIndexUpdatedEvent(); - - void StreamConformAppendedEvent(const AudioRenderingParams& params); - -}; - -OLIVE_NAMESPACE_EXIT - -#endif // INDEXMANAGER_H diff --git a/app/render/backend/renderbackend.cpp b/app/render/backend/renderbackend.cpp index 38d07ad35..8a4acb21e 100644 --- a/app/render/backend/renderbackend.cpp +++ b/app/render/backend/renderbackend.cpp @@ -24,7 +24,6 @@ #include #include "core.h" -#include "render/backend/indexmanager.h" #include "window/mainwindow/mainwindow.h" OLIVE_NAMESPACE_ENTER @@ -37,8 +36,6 @@ RenderBackend::RenderBackend(QObject *parent) : { // FIXME: Don't create in CLI mode cancel_dialog_ = new RenderCancelDialog(Core::instance()->main_window()); - - connect(IndexManager::instance(), &IndexManager::StreamIndexUpdated, this, &RenderBackend::IndexUpdated); } bool RenderBackend::Init() @@ -473,7 +470,6 @@ void RenderBackend::InitWorkers() // Connect cancel dialog to it connect(processor, &RenderWorker::CompletedCache, cancel_dialog_, &RenderCancelDialog::WorkerDone, Qt::QueuedConnection); - connect(processor, &RenderWorker::FootageUnavailable, this, &RenderBackend::FootageUnavailable, Qt::QueuedConnection); // Finally, we can move it to its own thread processor->moveToThread(thread); @@ -486,83 +482,6 @@ void RenderBackend::InitWorkers() processor_busy_state_.fill(false); } -void RenderBackend::FootageUnavailable(StreamPtr stream, Decoder::RetrieveState state, const TimeRange &range, const rational &stream_time) -{ - if (state == Decoder::kFailedToOpen){ - - qWarning() << "For range" << range.in() << "-" << range.out() << stream->footage()->filename() << "stream" << stream->index() << "failed to open"; - - } else if (state == Decoder::kIndexUnavailable) { - - FootageWaitInfo info = {stream, range, stream_time}; - - if (footage_wait_info_.contains(info)) { - return; - } - - qDebug() << "Waiting for" << stream.get() << "time" << stream_time.toDouble() << "for frame" << range.in(); - - if (IndexManager::instance()->IsIndexing(stream)) { - - footage_wait_info_.append(info); - - } else if ((stream->type() == Stream::kVideo && std::static_pointer_cast(stream)->is_frame_index_ready()) - || (stream->type() == Stream::kAudio && std::static_pointer_cast(stream)->index_done())) { - - // Index JUST finished, requeue this time - InvalidateCache(range, nullptr); - - } else { - - // Start indexing process - footage_wait_info_.append(info); - IndexManager::instance()->StartIndexingStream(stream); - - } - - } -} - -void RenderBackend::IndexUpdated(Stream* stream) -{ - for (int i=0;itype() == Stream::kVideo) { - - VideoStream* video_stream = static_cast(stream); - - if (video_stream->get_closest_timestamp_in_frame_index(info.stream_time) >= 0) { - // This index now has this frame, we can re-render it - qDebug() << "Re-ICing video" << info.affected_range.in().toDouble() << "to" << info.affected_range.out().toDouble(); - footage_ready = true; - } - - } else if (stream->type() == Stream::kAudio) { - - AudioStream* audio_stream = static_cast(stream); - - if (audio_stream->index_length() >= info.stream_time) { - // The index now has this audio, we can re-render it - qDebug() << "Re-ICing audio" << info.affected_range.in().toDouble() << "to" << info.affected_range.out().toDouble(); - footage_ready = true; - } - - } - - if (footage_ready) { - InvalidateCache(info.affected_range, nullptr); - footage_wait_info_.removeAt(i); - i--; - } - } - } -} - bool RenderBackend::FootageWaitInfo::operator==(const RenderBackend::FootageWaitInfo &rhs) const { return rhs.stream == stream diff --git a/app/render/backend/renderbackend.h b/app/render/backend/renderbackend.h index 1e8db284a..1431d00df 100644 --- a/app/render/backend/renderbackend.h +++ b/app/render/backend/renderbackend.h @@ -167,11 +167,6 @@ private: QList footage_wait_info_; -private slots: - void FootageUnavailable(StreamPtr stream, Decoder::RetrieveState state, const TimeRange& path, const rational& stream_time); - - void IndexUpdated(Stream *stream); - }; OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/renderworker.cpp b/app/render/backend/renderworker.cpp index 8ba8409f2..c2b1b2311 100644 --- a/app/render/backend/renderworker.cpp +++ b/app/render/backend/renderworker.cpp @@ -108,11 +108,6 @@ bool RenderWorker::IsStarted() return started_; } -void RenderWorker::ReportUnavailableFootage(StreamPtr stream, Decoder::RetrieveState state, const rational& stream_time) -{ - emit FootageUnavailable(stream, state, path_.range(), stream_time); -} - void RenderWorker::InputProcessingEvent(NodeInput* input, const TimeRange& input_time, NodeValueTable *table) { // Exception for Footage types where we actually retrieve some Footage data from a decoder @@ -124,13 +119,8 @@ void RenderWorker::InputProcessingEvent(NodeInput* input, const TimeRange& input if (decoder) { - Decoder::RetrieveState state = decoder->GetRetrieveState(input_time.out()); + FrameToValue(decoder, stream, input_time, table); - if (state == Decoder::kReady) { - FrameToValue(decoder, stream, input_time, table); - } else { - ReportUnavailableFootage(stream, state, input_time.out()); - } } } } diff --git a/app/render/backend/renderworker.h b/app/render/backend/renderworker.h index c0d3d35e3..17d8e600f 100644 --- a/app/render/backend/renderworker.h +++ b/app/render/backend/renderworker.h @@ -48,8 +48,6 @@ public slots: signals: void CompletedCache(OLIVE_NAMESPACE::NodeDependency dep, OLIVE_NAMESPACE::NodeValueTable data, qint64 job_time); - void FootageUnavailable(OLIVE_NAMESPACE::StreamPtr stream, OLIVE_NAMESPACE::Decoder::RetrieveState state, const OLIVE_NAMESPACE::TimeRange& range, const OLIVE_NAMESPACE::rational& stream_time); - protected: virtual bool InitInternal() = 0; @@ -61,8 +59,6 @@ protected: virtual void FrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeRange &range, NodeValueTable* table) = 0; - virtual void ReportUnavailableFootage(StreamPtr stream, Decoder::RetrieveState state, const rational& stream_time); - virtual void InputProcessingEvent(NodeInput *input, const TimeRange &input_time, NodeValueTable* table) override; virtual void ProcessNodeEvent(const Node *node, const TimeRange &range, NodeValueDatabase &input_params, NodeValueTable &output_params) override; diff --git a/app/render/backend/videorenderworker.cpp b/app/render/backend/videorenderworker.cpp index f64a0bdbd..9b6ff8af6 100644 --- a/app/render/backend/videorenderworker.cpp +++ b/app/render/backend/videorenderworker.cpp @@ -295,14 +295,6 @@ NodeValueTable VideoRenderWorker::RenderBlock(const TrackOutput *track, const Ti return table; } -void VideoRenderWorker::ReportUnavailableFootage(StreamPtr stream, Decoder::RetrieveState state, const rational &stream_time) -{ - emit FootageUnavailable(stream, - state, - TimeRange(CurrentPath().in(), CurrentPath().in() + video_params().time_base()), - stream_time); -} - ColorProcessorCache *VideoRenderWorker::color_cache() { return &color_cache_; diff --git a/app/render/backend/videorenderworker.h b/app/render/backend/videorenderworker.h index b1813b9b8..fb2661a0d 100644 --- a/app/render/backend/videorenderworker.h +++ b/app/render/backend/videorenderworker.h @@ -100,8 +100,6 @@ protected: virtual NodeValueTable RenderBlock(const TrackOutput *track, const TimeRange& range) override; - virtual void ReportUnavailableFootage(StreamPtr stream, Decoder::RetrieveState state, const rational& stream_time) override; - ColorProcessorCache* color_cache(); private: diff --git a/app/task/CMakeLists.txt b/app/task/CMakeLists.txt index d38d30ab4..914998798 100644 --- a/app/task/CMakeLists.txt +++ b/app/task/CMakeLists.txt @@ -15,7 +15,6 @@ # along with this program. If not, see . add_subdirectory(conform) -add_subdirectory(index) set(OLIVE_SOURCES ${OLIVE_SOURCES} diff --git a/app/task/conform/conform.cpp b/app/task/conform/conform.cpp index 2350f5f19..7202c572b 100644 --- a/app/task/conform/conform.cpp +++ b/app/task/conform/conform.cpp @@ -42,7 +42,7 @@ void ConformTask::Action() connect(decoder.get(), &Decoder::IndexProgress, this, &ConformTask::ProgressChanged); - decoder->Conform(params_, &IsCancelled()); + decoder->ConformAudio(&IsCancelled(), params_); emit Succeeded(); } diff --git a/app/task/index/CMakeLists.txt b/app/task/index/CMakeLists.txt deleted file mode 100644 index 0d3608dc2..000000000 --- a/app/task/index/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -# Olive - Non-Linear Video Editor -# Copyright (C) 2019 Olive Team -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -set(OLIVE_SOURCES - ${OLIVE_SOURCES} - task/index/index.h - task/index/index.cpp - PARENT_SCOPE -) diff --git a/app/task/index/index.cpp b/app/task/index/index.cpp deleted file mode 100644 index 0fb6393b7..000000000 --- a/app/task/index/index.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#include "index.h" - -#include "codec/decoder.h" -#include "codec/ffmpeg/ffmpegdecoder.h" - -OLIVE_NAMESPACE_ENTER - -IndexTask::IndexTask(StreamPtr stream) : - stream_(stream) -{ - SetTitle(tr("Indexing %1:%2").arg(stream_->footage()->filename(), QString::number(stream_->index()))); -} - -void IndexTask::Action() -{ - if (stream_->footage()->decoder().isEmpty()) { - emit Failed(QStringLiteral("Stream has no decoder")); - } else { - DecoderPtr decoder = Decoder::CreateFromID(stream_->footage()->decoder()); - - decoder->set_stream(stream_); - - connect(decoder.get(), &Decoder::IndexProgress, this, &IndexTask::ProgressChanged); - - decoder->Index(&IsCancelled()); - - emit Succeeded(); - } -} - -OLIVE_NAMESPACE_EXIT diff --git a/app/task/index/index.h b/app/task/index/index.h deleted file mode 100644 index a470866fe..000000000 --- a/app/task/index/index.h +++ /dev/null @@ -1,44 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2019 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#ifndef INDEXTASK_H -#define INDEXTASK_H - -#include "project/item/footage/footage.h" -#include "task/task.h" - -OLIVE_NAMESPACE_ENTER - -class IndexTask : public Task -{ -public: - IndexTask(StreamPtr stream); - -protected: - virtual void Action() override; - -private: - StreamPtr stream_; - -}; - -OLIVE_NAMESPACE_EXIT - -#endif // INDEXTASK_H