diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index cd3554872..2cccc4b1c 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -45,7 +45,7 @@ Decoder::Decoder(Stream *fs) : { } -StreamPtr Decoder::stream() +StreamPtr Decoder::stream() const { return stream_; } @@ -283,19 +283,6 @@ void Decoder::ConformInternal(SwrContext* resampler, WaveOutput* output, const c QString Decoder::GetConformedFilename(const AudioRenderingParams ¶ms) { QString index_fn = GetIndexFilename(); - WaveInput input(GetIndexFilename()); - - // FIXME: No handling if input failed to open/is corrupt - if (input.open()) { - // If the parameters are equal, nothing to be done - AudioRenderingParams index_params = input.params(); - input.close(); - - if (index_params == params) { - // Source file matches perfectly, no conform required - return index_fn; - } - } index_fn.append('.'); index_fn.append(QString::number(params.sample_rate())); diff --git a/app/codec/decoder.h b/app/codec/decoder.h index 7f273f936..1f9179e96 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -74,7 +74,7 @@ public: virtual QString id() = 0; - StreamPtr stream(); + StreamPtr stream() const; void set_stream(StreamPtr fs); /** @@ -253,7 +253,7 @@ protected: * Retrieves the absolute filename of the index file for this stream. Decoder must be open for * this to work correctly. */ - virtual QString GetIndexFilename() = 0; + virtual QString GetIndexFilename() const = 0; /** * @brief Get the destination filename of an audio stream conformed to a set of parameters diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 96e4a6ad9..17925d5c0 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -34,6 +34,7 @@ extern "C" { #include #include #include +#include #include "codec/waveinput.h" #include "common/define.h" @@ -41,6 +42,7 @@ extern "C" { #include "common/functiontimer.h" #include "common/timecodefunctions.h" #include "ffmpegcommon.h" +#include "render/backend/videorenderframecache.h" #include "render/diskmanager.h" #include "render/pixelformat.h" @@ -148,6 +150,52 @@ FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode, const int &divid int64_t target_ts = Timecode::time_to_timestamp(timecode, time_base_) + start_time_; + VideoStreamPtr vs = std::static_pointer_cast(stream()); + + if (vs->using_proxy()) { + QString proxy_fn = GetProxyFilename(vs->using_proxy()); + + int64_t index_ts = vs->get_closest_timestamp_in_frame_index(target_ts); + + if (target_ts > -1) { + // Use this timestamp instead - even if we fall through to decoding manually, it'll be more + // accurate than the one we calculated earlier + target_ts = index_ts; + + QString frame_filename = GetProxyFrameFilename(target_ts, vs->using_proxy()); + + if (QFileInfo::exists(frame_filename)) { + auto in = OIIO::ImageInput::open(frame_filename.toStdString()); + + if (in) { + FramePtr copy = Frame::Create(); + copy->set_video_params(VideoRenderingParams(GetScaledDimension(vs->width(), vs->using_proxy()), + GetScaledDimension(vs->height(), vs->using_proxy()), + native_pix_fmt_)); + copy->set_timestamp(Timecode::timestamp_to_time(target_ts, time_base_)); + copy->set_sample_aspect_ratio(aspect_ratio_); + copy->allocate(); + + // We're running one "decoder" per thread already, no need to spawn more than that + in->threads(1); + + in->read_image(PixelFormat::GetOIIOTypeDesc(native_pix_fmt_), + copy->data(), + OIIO::AutoStride, + copy->linesize_bytes()); + + in->close(); + +#if OIIO_VERSION < 10903 + OIIO::ImageInput::destroy(in); +#endif + + return copy; + } + } + } + } + FFmpegDecoderInstance* working_instance = nullptr; FFmpegFramePool::ElementPtr return_frame = nullptr; @@ -264,8 +312,6 @@ FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode, const int &divid InitScaler(divider); } - VideoStream* vs = static_cast(stream().get()); - // Create frame to return FramePtr copy = Frame::Create(); copy->set_video_params(VideoRenderingParams(GetScaledDimension(vs->width(), divider), @@ -587,17 +633,61 @@ void FFmpegDecoder::Error(const QString &s) ClearResources(); } +QMutex scaler_lock; +void SaveCacheFrame(SwsContext* scaler, + AVFrame* frame, + VideoRenderingParams params, + QString dst_fn) +{ + QByteArray converted_buffer(PixelFormat::GetBufferSize(params.format(), + params.width(), + params.height()), + Qt::Uninitialized); + + uint8_t* converted_data = reinterpret_cast(converted_buffer.data()); + int converted_linesize = PixelFormat::GetBufferSize(params.format(), + params.width(), + 1); + + scaler_lock.lock(); + sws_scale(scaler, + frame->data, + frame->linesize, + 0, + frame->height, + &converted_data, + &converted_linesize); + scaler_lock.unlock(); + + if (!VideoRenderFrameCache::SaveCacheFrame(dst_fn, converted_buffer.data(), params)) { + qCritical() <<" Failed to save cache frame" << dst_fn; + } + + av_frame_free(&frame); +} + bool FFmpegDecoder::ProxyVideo(const QAtomicInt *cancelled, int divider) { VideoStreamPtr video_stream = std::static_pointer_cast(stream()); - QString frame_index_file = GetIndexFilename().append('d').append(QString::number(divider)); + QString proxy_filename = GetProxyFilename(divider); - if (QFileInfo::exists(frame_index_file)) { + if (QFileInfo::exists(proxy_filename)) { // A proxy of this type already exists so we can do nothing - video_stream->set_proxy(divider); - return true; + QFile index_file(proxy_filename); + if (index_file.open(QFile::ReadOnly)) { + QVector index(index_file.size() / sizeof(int64_t)); + + index_file.read(reinterpret_cast(index.data()), + index_file.size()); + + index_file.close(); + + video_stream->set_proxy(divider, index); + + return true; + } } @@ -610,8 +700,8 @@ bool FFmpegDecoder::ProxyVideo(const QAtomicInt *cancelled, int divider) AVPixelFormat ideal_fmt = FFmpegCommon::GetCompatiblePixelFormat(src_fmt); PixelFormat::Format native_fmt = GetNativePixelFormat(ideal_fmt); - int divided_width = instance.stream()->codecpar->width; - int divided_height = instance.stream()->codecpar->height; + int divided_width = GetScaledDimension(instance.stream()->codecpar->width, divider); + int divided_height = GetScaledDimension(instance.stream()->codecpar->height, divider); SwsContext* scaler = sws_getContext(instance.stream()->codecpar->width, instance.stream()->codecpar->height, @@ -625,18 +715,12 @@ bool FFmpegDecoder::ProxyVideo(const QAtomicInt *cancelled, int divider) 0); AVPacket* pkt = av_packet_alloc(); - AVFrame* frame = av_frame_alloc(); QVector frame_index; + QVector< QFuture > futures; - QByteArray converted_buffer(PixelFormat::GetBufferSize(native_fmt, - divided_width, - divided_height), - Qt::Uninitialized); - - uint8_t* converted_data = reinterpret_cast(converted_buffer.data()); - int converted_linesize = PixelFormat::GetBufferSize(native_fmt, - divided_width, - 1); + VideoRenderingParams converted_params(divided_width, + divided_height, + native_fmt); bool succeeded = false; @@ -645,6 +729,8 @@ bool FFmpegDecoder::ProxyVideo(const QAtomicInt *cancelled, int divider) break; } + AVFrame* frame = av_frame_alloc(); + ret = instance.GetFrame(pkt, frame); // Handle errors @@ -657,49 +743,41 @@ bool FFmpegDecoder::ProxyVideo(const QAtomicInt *cancelled, int divider) qWarning() << "Failed to proxy:" << ret << err_str; } + av_frame_free(&frame); break; } - sws_scale(scaler, - frame->data, - frame->linesize, - 0, - frame->height, - &converted_data, - &converted_linesize); - - QString dst_fn = GetIndexFilename() - .append(QString::number(frame->pts)) - .append(QStringLiteral(".tiff")); - - std::string dst_std_fn = dst_fn.toStdString(); - - auto out = OIIO::ImageOutput::create(dst_std_fn); - - if (out) { - - out->open(dst_std_fn, - OIIO::ImageSpec(divided_width, - divided_height, - PixelFormat::ChannelCount(native_fmt), - PixelFormat::GetOIIOTypeDesc(native_fmt))); - - out->write_image(PixelFormat::GetOIIOTypeDesc(native_fmt), converted_data); - - out->close(); - -#if OIIO_VERSION < 10903 - OIIO::ImageOutput::destroy(out); -#endif - } - frame_index.append(frame->pts); SignalProcessingProgress(frame->pts); + + QFuture future = QtConcurrent::run(SaveCacheFrame, + scaler, + frame, + converted_params, + GetProxyFrameFilename(frame->pts, divider)); + futures.append(future); + } + + if (succeeded) { + QFile index_output(proxy_filename); + + if (index_output.open(QFile::WriteOnly)) { + index_output.write(reinterpret_cast(frame_index.constData()), + frame_index.size() * sizeof(int64_t)); + + index_output.close(); + } + + video_stream->set_proxy(divider, frame_index); + } + + // Wait for all conversions to finish + for (int i=0;ifootage()->filename())) .append(QString::number(stream()->index())); } +QString FFmpegDecoder::GetProxyFilename(int divider) const +{ + return GetIndexFilename().append('d').append(QString::number(divider)); +} + int FFmpegDecoder::GetScaledDimension(int dim, int divider) { return dim / divider; @@ -1184,6 +1267,14 @@ void FFmpegDecoder::FreeScaler() } } +QString FFmpegDecoder::GetProxyFrameFilename(const int64_t ×tamp, const int& divider) const +{ + QString dst_fn = GetProxyFilename(divider); + dst_fn.append(QString::number(timestamp)); + dst_fn.append(VideoRenderFrameCache::GetFormatExtension(native_pix_fmt_)); + return dst_fn; +} + int64_t FFmpegDecoderInstance::RangeStart() const { if (cached_frames_.isEmpty()) { diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index 4b34cce8d..f02e37f75 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -166,13 +166,17 @@ private: */ void FFmpegError(int error_code); - virtual QString GetIndexFilename() override; + virtual QString GetIndexFilename() const override; + + QString GetProxyFilename(int divider) const; void ClearResources(); void InitScaler(int divider); void FreeScaler(); + QString GetProxyFrameFilename(const int64_t& timestamp, const int ÷r) const; + static int GetScaledDimension(int dim, int divider); static PixelFormat::Format GetNativePixelFormat(AVPixelFormat pix_fmt); diff --git a/app/codec/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp index 1083cab30..5a2eac463 100644 --- a/app/codec/oiio/oiiodecoder.cpp +++ b/app/codec/oiio/oiiodecoder.cpp @@ -215,7 +215,7 @@ bool OIIODecoder::SupportsVideo() return true; } -QString OIIODecoder::GetIndexFilename() +QString OIIODecoder::GetIndexFilename() const { return QString(); } diff --git a/app/codec/oiio/oiiodecoder.h b/app/codec/oiio/oiiodecoder.h index 3d265f1a9..2bc03d169 100644 --- a/app/codec/oiio/oiiodecoder.h +++ b/app/codec/oiio/oiiodecoder.h @@ -45,7 +45,7 @@ public: virtual bool SupportsVideo() override; - virtual QString GetIndexFilename() override; + virtual QString GetIndexFilename() const override; static void FrameToBuffer(FramePtr frame, OIIO::ImageBuf* buf); diff --git a/app/project/item/footage/videostream.cpp b/app/project/item/footage/videostream.cpp index 25e67f586..fe02f5c58 100644 --- a/app/project/item/footage/videostream.cpp +++ b/app/project/item/footage/videostream.cpp @@ -26,8 +26,6 @@ OLIVE_NAMESPACE_ENTER -const int64_t VideoStream::kEndTimestamp = AV_NOPTS_VALUE; - VideoStream::VideoStream() : start_time_(0), is_image_sequence_(false), @@ -102,15 +100,15 @@ int VideoStream::using_proxy() return using_proxy_; } -void VideoStream::set_proxy(const int ÷r) +void VideoStream::set_proxy(const int ÷r, const QVector &index) { QMutexLocker locker(proxy_access_lock()); using_proxy_ = divider; + frame_index_ = index; is_generating_proxy_ = false; } -/* int64_t VideoStream::get_closest_timestamp_in_frame_index(const rational &time) { // Get rough approximation of what the timestamp would be in this timebase @@ -122,45 +120,31 @@ int64_t VideoStream::get_closest_timestamp_in_frame_index(const rational &time) int64_t VideoStream::get_closest_timestamp_in_frame_index(int64_t timestamp) { - QMutexLocker locker(&index_access_lock_); + QMutexLocker locker(proxy_access_lock()); - if (frame_index_.isEmpty()) { - return -1; - } + if (!frame_index_.isEmpty()) { + if (timestamp <= frame_index_.first()) { + return frame_index_.first(); + } else if (timestamp >= frame_index_.last()) { + return frame_index_.last(); + } else { + // Use index to find closest frame in file + for (int i=1;i timestamp) { - return frame_index_.at(i - 1); + if (this_ts == timestamp) { + return timestamp; + } else if (this_ts > timestamp) { + return frame_index_.at(i - 1); + } + } } } - if (frame_index_.last() == kEndTimestamp) { - // Index is done - return frame_index_.last(); - } else { - // Index is not done yet - return -1; - } + return -1; } +/* void VideoStream::clear_frame_index() { { diff --git a/app/project/item/footage/videostream.h b/app/project/item/footage/videostream.h index f50093af2..837fcafd9 100644 --- a/app/project/item/footage/videostream.h +++ b/app/project/item/footage/videostream.h @@ -31,8 +31,6 @@ class VideoStream : public ImageStream public: VideoStream(); - static const int64_t kEndTimestamp; - virtual QString description() const override; /** @@ -49,9 +47,9 @@ public: bool is_image_sequence() const; void set_image_sequence(bool e); - /* int64_t get_closest_timestamp_in_frame_index(const rational& time); int64_t get_closest_timestamp_in_frame_index(int64_t timestamp); + /* void clear_frame_index(); void append_frame_index(const int64_t& ts); bool is_frame_index_ready(); @@ -64,7 +62,7 @@ public: bool is_generating_proxy(); bool try_start_proxy(); int using_proxy(); - void set_proxy(const int& divider); + void set_proxy(const int& divider, const QVector& index); private: rational frame_rate_; diff --git a/app/render/backend/videorenderbackend.cpp b/app/render/backend/videorenderbackend.cpp index 7452b8ab7..db61fc0da 100644 --- a/app/render/backend/videorenderbackend.cpp +++ b/app/render/backend/videorenderbackend.cpp @@ -263,17 +263,12 @@ TimeRange VideoRenderBackend::PopNextFrameFromQueue() return TimeRange(frame_range.in(), frame_range.in()); } -void VideoRenderBackend::ThreadCompletedDownload(NodeDependency dep, qint64 job_time, QByteArray hash, bool texture_existed) +void VideoRenderBackend::ThreadCompletedDownload(NodeDependency dep, qint64 job_time, QByteArray hash) { SetWorkerBusyState(static_cast(sender()), false); SetFrameHash(dep, hash, job_time); - // Register frame with the disk manager - if (texture_existed && operating_mode_ & VideoRenderWorker::kDownloadOnly) { - DiskManager::instance()->CreatedFile(frame_cache()->CachePathName(hash, params_.format()), hash); - } - QList hashes_with_time = frame_cache()->FramesWithHash(hash); foreach (const rational& t, hashes_with_time) { diff --git a/app/render/backend/videorenderbackend.h b/app/render/backend/videorenderbackend.h index 852d075ea..fe7bc6f1b 100644 --- a/app/render/backend/videorenderbackend.h +++ b/app/render/backend/videorenderbackend.h @@ -136,7 +136,7 @@ private: bool pop_toggle_; private slots: - void ThreadCompletedDownload(NodeDependency dep, qint64 job_time, QByteArray hash, bool texture_existed); + void ThreadCompletedDownload(NodeDependency dep, qint64 job_time, QByteArray hash); void ThreadSkippedFrame(NodeDependency dep, qint64 job_time, QByteArray hash); void ThreadHashAlreadyExists(NodeDependency dep, qint64 job_time, QByteArray hash); void ThreadGeneratedFrame(); diff --git a/app/render/backend/videorenderframecache.cpp b/app/render/backend/videorenderframecache.cpp index e1d841f45..28fbe0203 100644 --- a/app/render/backend/videorenderframecache.cpp +++ b/app/render/backend/videorenderframecache.cpp @@ -20,10 +20,16 @@ #include "videorenderframecache.h" +#include +#include +#include +#include #include #include +#include "codec/frame.h" #include "common/filefunctions.h" +#include "render/diskmanager.h" OLIVE_NAMESPACE_ENTER @@ -139,25 +145,126 @@ const QMap &VideoRenderFrameCache::time_hash_map() const return time_hash_map_; } +QString VideoRenderFrameCache::GetFormatExtension(const PixelFormat::Format &f) +{ + if (PixelFormat::FormatIsFloat(f)) { + return QStringLiteral(".exr"); + } else { + return QStringLiteral(".jpg"); + } +} + +void VideoRenderFrameCache::SaveCacheFrame(const QByteArray& hash, + char* data, + const VideoRenderingParams& vparam) const +{ + QString fn = CachePathName(hash, vparam.format()); + + if (SaveCacheFrame(fn, data, vparam)) { + // Register frame with the disk manager + DiskManager::instance()->CreatedFile(fn, hash); + } +} + QString VideoRenderFrameCache::CachePathName(const QByteArray& hash, const PixelFormat::Format& pix_fmt) const { - QString ext; - - if (pix_fmt == PixelFormat::PIX_FMT_RGB8 - || pix_fmt == PixelFormat::PIX_FMT_RGBA8 - || pix_fmt == PixelFormat::PIX_FMT_RGB16U - || pix_fmt == PixelFormat::PIX_FMT_RGBA16U) { - ext = QStringLiteral("jpg"); - } else { - ext = QStringLiteral("exr"); - } + QString ext = GetFormatExtension(pix_fmt); QDir cache_dir(QDir(FileFunctions::GetMediaCacheLocation()).filePath(QString(hash.left(1).toHex()))); cache_dir.mkpath("."); - QString filename = QStringLiteral("%1.%2").arg(QString(hash.mid(1).toHex()), ext); + QString filename = QStringLiteral("%1%2").arg(QString(hash.mid(1).toHex()), ext); return cache_dir.filePath(filename); } +bool VideoRenderFrameCache::SaveCacheFrame(const QString &filename, char *data, const VideoRenderingParams &vparam) +{ + switch (vparam.format()) { + case PixelFormat::PIX_FMT_RGB8: + case PixelFormat::PIX_FMT_RGBA8: + case PixelFormat::PIX_FMT_RGB16U: + case PixelFormat::PIX_FMT_RGBA16U: + { + // Integer types are stored in JPEG which we run through OIIO + + std::string fn_std = filename.toStdString(); + + auto out = OIIO::ImageOutput::create(fn_std); + + if (out) { + // Attempt to keep this write to one thread + out->threads(1); + + out->open(fn_std, OIIO::ImageSpec(vparam.width(), + vparam.height(), + PixelFormat::ChannelCount(vparam.format()), + PixelFormat::GetOIIOTypeDesc(vparam.format()))); + + out->write_image(PixelFormat::GetOIIOTypeDesc(vparam.format()), data); + + out->close(); + +#if OIIO_VERSION < 10903 + OIIO::ImageOutput::destroy(out); +#endif + + return true; + } else { + qCritical() << "Failed to write JPEG file:" << OIIO::geterror().c_str(); + return false; + } + } + case PixelFormat::PIX_FMT_RGB16F: + case PixelFormat::PIX_FMT_RGBA16F: + case PixelFormat::PIX_FMT_RGB32F: + case PixelFormat::PIX_FMT_RGBA32F: + { + // Floating point types are stored in EXR + Imf::PixelType pix_type; + + if (vparam.format() == PixelFormat::PIX_FMT_RGB16F + || vparam.format() == PixelFormat::PIX_FMT_RGBA16F) { + pix_type = Imf::HALF; + } else { + pix_type = Imf::FLOAT; + } + + Imf::Header header(vparam.effective_width(), + vparam.effective_height()); + header.channels().insert("R", Imf::Channel(pix_type)); + header.channels().insert("G", Imf::Channel(pix_type)); + header.channels().insert("B", Imf::Channel(pix_type)); + header.channels().insert("A", Imf::Channel(pix_type)); + + header.compression() = Imf::DWAA_COMPRESSION; + header.insert("dwaCompressionLevel", Imf::FloatAttribute(200.0f)); + + Imf::OutputFile out(filename.toUtf8(), header, 0); + + int bpc = PixelFormat::BytesPerChannel(vparam.format()); + + size_t xs = kRGBAChannels * bpc; + size_t ys = vparam.effective_width() * kRGBAChannels * bpc; + + Imf::FrameBuffer framebuffer; + framebuffer.insert("R", Imf::Slice(pix_type, data, xs, ys)); + framebuffer.insert("G", Imf::Slice(pix_type, data + bpc, xs, ys)); + framebuffer.insert("B", Imf::Slice(pix_type, data + 2*bpc, xs, ys)); + framebuffer.insert("A", Imf::Slice(pix_type, data + 3*bpc, xs, ys)); + out.setFrameBuffer(framebuffer); + + out.writePixels(vparam.effective_height()); + + return true; + } + case PixelFormat::PIX_FMT_INVALID: + case PixelFormat::PIX_FMT_COUNT: + qCritical() << "Unable to cache invalid pixel format" << vparam.format(); + break; + } + + return false; +} + OLIVE_NAMESPACE_EXIT diff --git a/app/render/backend/videorenderframecache.h b/app/render/backend/videorenderframecache.h index a9113f193..5c326e3f1 100644 --- a/app/render/backend/videorenderframecache.h +++ b/app/render/backend/videorenderframecache.h @@ -25,6 +25,7 @@ #include "common/rational.h" #include "render/pixelformat.h" +#include "render/videoparams.h" OLIVE_NAMESPACE_ENTER @@ -50,11 +51,6 @@ public: */ bool TryCache(const QByteArray& hash); - /** - * @brief Return the path of the cached image at this time - */ - QString CachePathName(const QByteArray &hash, const PixelFormat::Format& pix_fmt) const; - void SetCacheID(const QString& id); QByteArray TimeToHash(const rational& time) const; @@ -77,6 +73,16 @@ public: const QMap& time_hash_map() const; + static QString GetFormatExtension(const PixelFormat::Format& f); + + /** + * @brief Return the path of the cached image at this time + */ + QString CachePathName(const QByteArray &hash, const PixelFormat::Format& pix_fmt) const; + + static bool SaveCacheFrame(const QString& filename, char *data, const VideoRenderingParams &vparam); + void SaveCacheFrame(const QByteArray& hash, char *data, const VideoRenderingParams &vparam) const; + private: QMap time_hash_map_; diff --git a/app/render/backend/videorenderworker.cpp b/app/render/backend/videorenderworker.cpp index 9b6ff8af6..ffc568811 100644 --- a/app/render/backend/videorenderworker.cpp +++ b/app/render/backend/videorenderworker.cpp @@ -20,11 +20,6 @@ #include "videorenderworker.h" -#include -#include -#include -#include - #include "common/define.h" #include "common/functiontimer.h" #include "node/block/transition/transition.h" @@ -84,7 +79,7 @@ NodeValueTable VideoRenderWorker::RenderInternal(const NodeDependency& path, con if (!(operating_mode_ & kRenderOnly)) { // Emit only the hash - emit CompletedDownload(path, job_time, hash, false); + emit CompletedDownload(path, job_time, hash); } else if ((operating_mode_ & kHashOnly) && frame_cache_->HasHash(hash, video_params_.format())) { @@ -101,14 +96,14 @@ NodeValueTable VideoRenderWorker::RenderInternal(const NodeDependency& path, con // If we actually have a texture, download it into the disk cache if (!texture.isNull() || (!(operating_mode_ & kDownloadOnly))) { - Download(path.in(), texture, frame_cache_->CachePathName(hash, video_params_.format())); + Download(hash, path.in(), texture); } frame_cache_->RemoveHashFromCurrentlyCaching(hash); // Signal that this job is complete if (operating_mode_ & kDownloadOnly) { - emit CompletedDownload(path, job_time, hash, !texture.isNull()); + emit CompletedDownload(path, job_time, hash); } } else { @@ -162,92 +157,17 @@ void VideoRenderWorker::CloseInternal() download_buffer_.clear(); } -void VideoRenderWorker::Download(const rational& time, QVariant texture, QString filename) +void VideoRenderWorker::Download(const QByteArray& hash, const rational& time, QVariant texture) { if (operating_mode_ & kDownloadOnly) { TextureToBuffer(texture, download_buffer_.data(), 0); - switch (video_params().format()) { - case PixelFormat::PIX_FMT_RGB8: - case PixelFormat::PIX_FMT_RGBA8: - case PixelFormat::PIX_FMT_RGB16U: - case PixelFormat::PIX_FMT_RGBA16U: - { - // Integer types are stored in JPEG which we run through OIIO - - std::string fn_std = filename.toStdString(); - - auto out = OIIO::ImageOutput::create(fn_std); - - if (out) { - // Attempt to keep this write to one thread - out->threads(1); - - out->open(fn_std, OIIO::ImageSpec(video_params().effective_width(), - video_params().effective_height(), - PixelFormat::ChannelCount(video_params().format()), - PixelFormat::GetOIIOTypeDesc(video_params().format()))); - - out->write_image(PixelFormat::GetOIIOTypeDesc(video_params().format()), download_buffer_.data()); - - out->close(); - -#if OIIO_VERSION < 10903 - OIIO::ImageOutput::destroy(out); -#endif - } else { - qCritical() << "Failed to write JPEG file:" << OIIO::geterror().c_str(); - } - break; - } - case PixelFormat::PIX_FMT_RGB16F: - case PixelFormat::PIX_FMT_RGBA16F: - case PixelFormat::PIX_FMT_RGB32F: - case PixelFormat::PIX_FMT_RGBA32F: - { - // Floating point types are stored in EXR - Imf::PixelType pix_type; - - if (video_params().format() == PixelFormat::PIX_FMT_RGB16F - || video_params().format() == PixelFormat::PIX_FMT_RGBA16F) { - pix_type = Imf::HALF; - } else { - pix_type = Imf::FLOAT; - } - - Imf::Header header(video_params().effective_width(), - video_params().effective_height()); - header.channels().insert("R", Imf::Channel(pix_type)); - header.channels().insert("G", Imf::Channel(pix_type)); - header.channels().insert("B", Imf::Channel(pix_type)); - header.channels().insert("A", Imf::Channel(pix_type)); - - header.compression() = Imf::DWAA_COMPRESSION; - header.insert("dwaCompressionLevel", Imf::FloatAttribute(200.0f)); - - Imf::OutputFile out(filename.toUtf8(), header, 0); - - int bpc = PixelFormat::BytesPerChannel(video_params().format()); - - size_t xs = kRGBAChannels * bpc; - size_t ys = video_params().effective_width() * kRGBAChannels * bpc; - - Imf::FrameBuffer framebuffer; - framebuffer.insert("R", Imf::Slice(pix_type, download_buffer_.data(), xs, ys)); - framebuffer.insert("G", Imf::Slice(pix_type, download_buffer_.data() + bpc, xs, ys)); - framebuffer.insert("B", Imf::Slice(pix_type, download_buffer_.data() + 2*bpc, xs, ys)); - framebuffer.insert("A", Imf::Slice(pix_type, download_buffer_.data() + 3*bpc, xs, ys)); - out.setFrameBuffer(framebuffer); - - out.writePixels(video_params().effective_height()); - break; - } - case PixelFormat::PIX_FMT_INVALID: - case PixelFormat::PIX_FMT_COUNT: - qCritical() << "Unable to cache invalid pixel format" << video_params().format(); - break; - } + frame_cache_->SaveCacheFrame(hash, + download_buffer_.data(), + VideoRenderingParams(video_params_.effective_width(), + video_params_.effective_height(), + video_params_.format())); } else { diff --git a/app/render/backend/videorenderworker.h b/app/render/backend/videorenderworker.h index fb2661a0d..a4737fcde 100644 --- a/app/render/backend/videorenderworker.h +++ b/app/render/backend/videorenderworker.h @@ -73,7 +73,7 @@ public: void SetFrameGenerationParams(int width, int height, const QMatrix4x4 &matrix); signals: - void CompletedDownload(NodeDependency path, qint64 job_time, QByteArray hash, bool texture_existed); + void CompletedDownload(NodeDependency path, qint64 job_time, QByteArray hash); void HashAlreadyBeingCached(NodeDependency path, qint64 job_time, QByteArray hash); @@ -103,7 +103,7 @@ protected: ColorProcessorCache* color_cache(); private: - void Download(const rational &time, QVariant texture, QString filename); + void Download(const QByteArray &hash, const rational &time, QVariant texture); void ResizeDownloadBuffer();