From 3e57cdac1d412be81d875193953b6d6d2c497b43 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sat, 2 May 2020 22:51:29 +1000 Subject: [PATCH 01/15] use linesize workaround for setting OIIO pixels as well as getting --- app/codec/oiio/oiiodecoder.cpp | 33 +++++++++++++++++++++++++++++++-- app/codec/oiio/oiiodecoder.h | 2 ++ app/render/pixelformat.cpp | 6 +----- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/app/codec/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp index 52653df05..590c23706 100644 --- a/app/codec/oiio/oiiodecoder.cpp +++ b/app/codec/oiio/oiiodecoder.cpp @@ -231,6 +231,35 @@ QString OIIODecoder::GetIndexFilename() return QString(); } +void OIIODecoder::FrameToBuffer(FramePtr frame, OpenImageIO_v2_1::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 @@ -239,9 +268,9 @@ void OIIODecoder::BufferToFrame(OIIO::ImageBuf *buf, FramePtr frame) // // See more: https://github.com/OpenImageIO/oiio/pull/2487 // - for (int i=0;ispec().height;i++) { - int width_in_bytes = frame->width() * PixelFormat::BytesPerPixel(frame->format()); + 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, diff --git a/app/codec/oiio/oiiodecoder.h b/app/codec/oiio/oiiodecoder.h index 249a1a346..b56a49b46 100644 --- a/app/codec/oiio/oiiodecoder.h +++ b/app/codec/oiio/oiiodecoder.h @@ -48,6 +48,8 @@ public: virtual QString GetIndexFilename() override; + static void FrameToBuffer(FramePtr frame, OIIO::ImageBuf* buf); + static void BufferToFrame(OIIO::ImageBuf* buf, FramePtr frame); private: diff --git a/app/render/pixelformat.cpp b/app/render/pixelformat.cpp index 2ddd251c1..7c55e0c13 100644 --- a/app/render/pixelformat.cpp +++ b/app/render/pixelformat.cpp @@ -230,11 +230,7 @@ FramePtr PixelFormat::ConvertPixelFormat(FramePtr frame, const PixelFormat::Form // Set the pixels (this is necessary as opposed to an OIIO buffer wrapper since Frame has // linesizes) - src.set_pixels(OIIO::ROI(), - GetOIIOTypeDesc(frame->format()), - frame->const_data(), - OIIO::AutoStride, - frame->linesize_bytes()); + OIIODecoder::FrameToBuffer(frame, &src); // Create a destination OIIO buffer with our destination format OIIO::ImageBuf dst(OIIO::ImageSpec(converted->width(), From 2d0e5322f21d161e181439d98087b7925c03f078 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sat, 2 May 2020 22:55:45 +1000 Subject: [PATCH 02/15] oiiodecoder: fixed namespace issue --- app/codec/oiio/oiiodecoder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/codec/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp index 590c23706..e55e379ee 100644 --- a/app/codec/oiio/oiiodecoder.cpp +++ b/app/codec/oiio/oiiodecoder.cpp @@ -231,7 +231,7 @@ QString OIIODecoder::GetIndexFilename() return QString(); } -void OIIODecoder::FrameToBuffer(FramePtr frame, OpenImageIO_v2_1::ImageBuf *buf) +void OIIODecoder::FrameToBuffer(FramePtr frame, OIIO::ImageBuf *buf) { #if OIIO_VERSION < 20112 // From c5458c684377141c5440d68192136bdf806f35e6 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sun, 3 May 2020 00:44:14 +1000 Subject: [PATCH 03/15] timeline: use headers in waveform data files rather than sidecar metadata files --- app/render/backend/audiorenderworker.cpp | 20 ++++++++----------- .../view/timelineviewblockitem.cpp | 10 +++------- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/app/render/backend/audiorenderworker.cpp b/app/render/backend/audiorenderworker.cpp index c8d53c8b1..2365855a4 100644 --- a/app/render/backend/audiorenderworker.cpp +++ b/app/render/backend/audiorenderworker.cpp @@ -112,7 +112,14 @@ NodeValueTable AudioRenderWorker::RenderBlock(const TrackOutput *track, const Ti AudioRenderingParams waveform_params(SampleSummer::kSumSampleRate, audio_params_.channel_layout(), SampleFormat::SAMPLE_FMT_S32); int chunk_size = (audio_params().sample_rate() / waveform_params.sample_rate()); - qint64 start_offset = waveform_params.time_to_bytes(range_for_block.in() - b->in()); + { + // Write metadata header + SampleSummer::Info info; + info.channels = audio_params_.channel_count(); + wave_file.write(reinterpret_cast(&info), sizeof(SampleSummer::Info)); + } + + qint64 start_offset = sizeof(SampleSummer::Info) + waveform_params.time_to_bytes(range_for_block.in() - b->in()); qint64 length_offset = waveform_params.time_to_bytes(range_for_block.length()); qint64 end_offset = start_offset + length_offset; @@ -133,17 +140,6 @@ NodeValueTable AudioRenderWorker::RenderBlock(const TrackOutput *track, const Ti wave_file.close(); - // Write metadata about this waveform file - QFile wave_metadata(wave_fn.append(QStringLiteral(".meta"))); - if (wave_metadata.open(QFile::WriteOnly)) { - SampleSummer::Info info; - info.channels = audio_params_.channel_count(); - - wave_metadata.write(reinterpret_cast(&info), sizeof(SampleSummer::Info)); - - wave_metadata.close(); - } - if (src_block->type() == Block::kClip) { emit static_cast(src_block)->PreviewUpdated(); } diff --git a/app/widget/timelinewidget/view/timelineviewblockitem.cpp b/app/widget/timelinewidget/view/timelineviewblockitem.cpp index 81edaa2ed..1b63d0d3e 100644 --- a/app/widget/timelinewidget/view/timelineviewblockitem.cpp +++ b/app/widget/timelinewidget/view/timelineviewblockitem.cpp @@ -107,19 +107,15 @@ void TimelineViewBlockItem::paint(QPainter *painter, const QStyleOptionGraphicsI // Read metadata SampleSummer::Info info; - QFile wave_meta(wave_fn.append(QStringLiteral(".meta"))); - if (wave_meta.open(QFile::ReadOnly)) { - wave_meta.read(reinterpret_cast(&info), sizeof(SampleSummer::Info)); - wave_meta.close(); - } + memcpy(&info, w.data(), sizeof(SampleSummer::Info)); // Prevent divide by zero if (info.channels) { AudioWaveformView::DrawWaveform(painter, rect().toRect(), this->GetScale(), - reinterpret_cast(w.constData()), - w.size() / sizeof(SampleSummer::Sum), + reinterpret_cast(w.constData() + sizeof(SampleSummer::Info)), + (w.size() - sizeof(SampleSummer::Info)) / sizeof(SampleSummer::Sum), info.channels); } } From 80acd91d880f6819f3c3f6bad2fbdcdf818daa04 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sun, 3 May 2020 02:21:52 +1000 Subject: [PATCH 04/15] created script to auto-generate qt resource file from ocio config --- app/render/ocioconf/gen-qrc.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100755 app/render/ocioconf/gen-qrc.sh diff --git a/app/render/ocioconf/gen-qrc.sh b/app/render/ocioconf/gen-qrc.sh new file mode 100755 index 000000000..448f6b4b2 --- /dev/null +++ b/app/render/ocioconf/gen-qrc.sh @@ -0,0 +1,17 @@ +#!/bin/sh +ourbasename=$(basename "$0") + +rm ocioconf.qrc +echo "" >> ocioconf.qrc +echo " " >> ocioconf.qrc + +for f in $(find * -type f) +do + if [ "$f" != "CMakeLists.txt" ] && [ "$f" != "ocioconf.qrc" ] && [ "$f" != "$ourbasename" ] + then + echo " $f" >> ocioconf.qrc + fi +done + +echo " " >> ocioconf.qrc +echo "" >> ocioconf.qrc From 403a8a8656407a812d95bf3e4f0a6d4832bb2218 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sun, 3 May 2020 03:14:38 +1000 Subject: [PATCH 05/15] ocio: change lut edge size to 64 instead of 32 Doesn't "solve" the white inaccuracy issue, but does mitigate it somewhat. Also improves accuracy across the board. --- app/render/backend/opengl/openglshader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/render/backend/opengl/openglshader.cpp b/app/render/backend/opengl/openglshader.cpp index 21ff57752..ff75ad189 100644 --- a/app/render/backend/opengl/openglshader.cpp +++ b/app/render/backend/opengl/openglshader.cpp @@ -41,7 +41,7 @@ OpenGLShaderPtr OpenGLShader::CreateDefault(const QString &function_name, const } // copied from source code to OCIODisplay -const int OCIO_LUT3D_EDGE_SIZE = 32; +const int OCIO_LUT3D_EDGE_SIZE = 64; // copied from source code to OCIODisplay, expanded from 3*LUT3D_EDGE_SIZE*LUT3D_EDGE_SIZE*LUT3D_EDGE_SIZE const int OCIO_NUM_3D_ENTRIES = 3*OCIO_LUT3D_EDGE_SIZE*OCIO_LUT3D_EDGE_SIZE*OCIO_LUT3D_EDGE_SIZE; From a925476c3fac368a6333ee2f77678a649f5eaf68 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sun, 3 May 2020 05:23:36 +1000 Subject: [PATCH 06/15] began work on exr proxy task --- app/codec/decoder.cpp | 6 +- app/codec/decoder.h | 41 ++- app/codec/ffmpeg/ffmpegdecoder.cpp | 310 +++++++++--------- app/codec/ffmpeg/ffmpegdecoder.h | 5 +- app/core.cpp | 16 +- app/dialog/preferences/preferences.cpp | 17 +- .../projectproperties/projectproperties.cpp | 187 ++++++++--- .../projectproperties/projectproperties.h | 71 +++- app/project/item/footage/audiostream.cpp | 14 +- app/project/item/footage/audiostream.h | 1 - app/project/item/footage/stream.cpp | 16 +- app/project/item/footage/stream.h | 16 +- app/project/item/footage/videostream.cpp | 16 + app/project/item/footage/videostream.h | 9 +- app/project/project.h | 20 ++ 15 files changed, 469 insertions(+), 276 deletions(-) diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index b68cf7194..e090238b1 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -319,7 +319,11 @@ QString Decoder::GetConformedFilename(const AudioRenderingParams ¶ms) return index_fn; } -void Decoder::Index(const QAtomicInt *) +void Decoder::ProxyVideo(const QAtomicInt *, int ) +{ +} + +void Decoder::ProxyAudio(const QAtomicInt *) { } diff --git a/app/codec/decoder.h b/app/codec/decoder.h index ba38502c5..2a6e1a81d 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -216,37 +216,41 @@ public: static DecoderPtr CreateFromID(const QString& id); /** - * @brief Conform an audio stream to match certain parameters (audio only) + * @brief AUDIO ONLY: Conform an audio stream to match certain parameters * - * 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. + * 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. + * 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 Create an index for this media - * - * Indexes are used to improve speed and reliability of imported media. Calling Retrieve() will automatically check - * for an index and create one if it doesn't exist. - * - * Indexing is slow so it's recommended to do it in a background thread. Index() must be called while the Decoder is - * open, and does not automatically call Open() and Close() the Decoder. The caller must call thse manually. + * @brief VIDEO ONLY: Produce a compressed EXR proxy with the specified divider */ - virtual void Index(const QAtomicInt* cancelled); + virtual void ProxyVideo(const QAtomicInt* cancelled, int divider); /** - * @brief AUDIO ONLY: Returns whether a cached transcode of this audio matching the specified params already exists + * @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); + + /** + * @brief AUDIO ONLY: Returns whether a transcode of this audio matching the specified params + * already exists */ bool HasConformedVersion(const AudioRenderingParams& params); signals: /** - * @brief While indexing, this signal will provide progress as a percentage (0-100 inclusive) if available + * @brief While indexing, this signal will provide progress as a percentage (0-100 inclusive) if + * available */ void IndexProgress(int); @@ -256,7 +260,8 @@ protected: /** * @brief Returns the filename for the index * - * Retrieves the absolute filename of the index file for this stream. Decoder must be open for this to work correctly. + * Retrieves the absolute filename of the index file for this stream. Decoder must be open for + * this to work correctly. */ virtual QString GetIndexFilename() = 0; diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 79487e0e3..c7438cd37 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -623,24 +623,170 @@ void FFmpegDecoder::Error(const QString &s) ClearResources(); } -void FFmpegDecoder::Index(const QAtomicInt* cancelled) +void FFmpegDecoder::ProxyVideo(const QAtomicInt *cancelled, int divider) { + // Iterate through each video frame transcode each frame to compressed EXR QMutexLocker locker(stream()->index_process_lock()); - if (stream()->type() == Stream::kAudio) { + QByteArray fn_bytes = stream()->footage()->filename().toUtf8(); - 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())); + FFmpegDecoderInstance index_instance(fn_bytes.constData(), stream()->index()); - input.close(); + +} + +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()); + + 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; } - } else { - UnconditionalAudioIndex(cancelled); + + 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); + + AVPacket* pkt = av_packet_alloc(); + AVFrame* frame = av_frame_alloc(); + int ret; + + if (wave_out.open()) { + bool success = false; + + 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 { + 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); } } @@ -655,150 +801,6 @@ int FFmpegDecoder::GetScaledDimension(int dim, int divider) return dim / divider; } -void FFmpegDecoder::UnconditionalAudioIndex(const QAtomicInt* cancelled) -{ - // Iterate through each audio frame and extract the PCM data - - QByteArray fn_bytes = stream()->footage()->filename().toUtf8(); - - FFmpegDecoderInstance index_instance(fn_bytes.constData(), 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); - - AVPacket* pkt = av_packet_alloc(); - AVFrame* frame = av_frame_alloc(); - int ret; - - if (wave_out.open()) { - bool success = false; - - 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 { - 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); -} - 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 f05d29541..44d102520 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -144,7 +144,8 @@ public: virtual bool SupportsVideo() override; virtual bool SupportsAudio() override; - virtual void Index(const QAtomicInt *cancelled) override; + virtual void ProxyVideo(const QAtomicInt* cancelled, int divider) override; + virtual void ProxyAudio(const QAtomicInt* cancelled) override; private: /** @@ -168,8 +169,6 @@ private: virtual QString GetIndexFilename() override; - void UnconditionalAudioIndex(const QAtomicInt* cancelled); - void ClearResources(); void InitScaler(int divider); diff --git a/app/core.cpp b/app/core.cpp index 15699cb5c..93dbc4585 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -370,8 +370,17 @@ void Core::DialogPreferencesShow() void Core::DialogProjectPropertiesShow() { - ProjectPropertiesDialog ppd(GetActiveProject().get(), main_window_); - ppd.exec(); + ProjectPtr proj = GetActiveProject(); + + if (proj) { + ProjectPropertiesDialog ppd(proj.get(), main_window_); + ppd.exec(); + } else { + QMessageBox::critical(main_window_, + tr("No Active Project"), + tr("No project is currently open to set the properties for"), + QMessageBox::Ok); + } } void Core::DialogExportShow() @@ -783,6 +792,7 @@ QList Core::SupportedChannelLayouts() channel_layouts.append(AV_CH_LAYOUT_MONO); channel_layouts.append(AV_CH_LAYOUT_STEREO); + channel_layouts.append(AV_CH_LAYOUT_2_1); channel_layouts.append(AV_CH_LAYOUT_5POINT1); channel_layouts.append(AV_CH_LAYOUT_7POINT1); @@ -806,6 +816,8 @@ QString Core::ChannelLayoutToString(const uint64_t &layout) return tr("Mono"); case AV_CH_LAYOUT_STEREO: return tr("Stereo"); + case AV_CH_LAYOUT_2_1: + return tr("2.1"); case AV_CH_LAYOUT_5POINT1: return tr("5.1"); case AV_CH_LAYOUT_7POINT1: diff --git a/app/dialog/preferences/preferences.cpp b/app/dialog/preferences/preferences.cpp index bddb46e62..872cab711 100644 --- a/app/dialog/preferences/preferences.cpp +++ b/app/dialog/preferences/preferences.cpp @@ -62,16 +62,19 @@ PreferencesDialog::PreferencesDialog(QWidget *parent, QMenuBar* main_menu_bar) : splitter->addWidget(list_widget_); splitter->addWidget(preference_pane_stack_); - QDialogButtonBox* buttonBox = new QDialogButtonBox(this); - buttonBox->setOrientation(Qt::Horizontal); - buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); + QDialogButtonBox* button_box = new QDialogButtonBox(this); + button_box->setOrientation(Qt::Horizontal); + button_box->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); - layout->addWidget(buttonBox); + layout->addWidget(button_box); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(button_box, &QDialogButtonBox::accepted, this, &PreferencesDialog::accept); + connect(button_box, &QDialogButtonBox::rejected, this, &PreferencesDialog::reject); - connect(list_widget_, SIGNAL(currentRowChanged(int)), preference_pane_stack_, SLOT(setCurrentIndex(int))); + connect(list_widget_, + &QListWidget::currentRowChanged, + preference_pane_stack_, + &QStackedWidget::setCurrentIndex); } void PreferencesDialog::accept() diff --git a/app/dialog/projectproperties/projectproperties.cpp b/app/dialog/projectproperties/projectproperties.cpp index 24e53a250..6e79a1aae 100644 --- a/app/dialog/projectproperties/projectproperties.cpp +++ b/app/dialog/projectproperties/projectproperties.cpp @@ -22,7 +22,6 @@ #include #include -#include #include #include #include @@ -45,67 +44,119 @@ ProjectPropertiesDialog::ProjectPropertiesDialog(Project* p, QWidget *parent) : setWindowTitle(tr("Project Properties for '%1'").arg(working_project_->name())); - QGroupBox* color_group = new QGroupBox(); - color_group->setTitle(tr("Color Management")); + { + // Color management group + QGroupBox* color_group = new QGroupBox(); + color_group->setTitle(tr("Color Management")); - QGridLayout* color_layout = new QGridLayout(color_group); + QGridLayout* color_layout = new QGridLayout(color_group); - int row = 0; + int row = 0; - color_layout->addWidget(new QLabel(tr("OpenColorIO Configuration:")), row, 0); + color_layout->addWidget(new QLabel(tr("OpenColorIO Configuration:")), row, 0); - ocio_filename_ = new QLineEdit(); - ocio_filename_->setPlaceholderText(tr("(default)")); - color_layout->addWidget(ocio_filename_, row, 1); + ocio_filename_ = new QLineEdit(); + ocio_filename_->setPlaceholderText(tr("(default)")); + color_layout->addWidget(ocio_filename_, row, 1); - row++; + row++; - color_layout->addWidget(new QLabel(tr("Default Input Color Space:")), row, 0); + color_layout->addWidget(new QLabel(tr("Default Input Color Space:")), row, 0); - default_input_colorspace_ = new QComboBox(); - color_layout->addWidget(default_input_colorspace_, row, 1, 1, 2); + default_input_colorspace_ = new QComboBox(); + color_layout->addWidget(default_input_colorspace_, row, 1, 1, 2); - row++; + row++; - QPushButton* browse_btn = new QPushButton(tr("Browse")); - color_layout->addWidget(browse_btn, 0, 2); - connect(browse_btn, SIGNAL(clicked(bool)), this, SLOT(BrowseForOCIOConfig())); + QPushButton* browse_btn = new QPushButton(tr("Browse")); + color_layout->addWidget(browse_btn, 0, 2); + connect(browse_btn, &QPushButton::clicked, this, &ProjectPropertiesDialog::BrowseForOCIOConfig); - layout->addWidget(color_group); + layout->addWidget(color_group); - QDialogButtonBox* dialog_btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal); - layout->addWidget(dialog_btns); - connect(dialog_btns, SIGNAL(accepted()), this, SLOT(accept())); - connect(dialog_btns, SIGNAL(rejected()), this, SLOT(reject())); + ocio_filename_->setText(working_project_->color_manager()->GetConfigFilename()); - if (working_project_ == nullptr) { - QMessageBox::critical(this, - tr("No Active Project"), - tr("No project is currently open to set the properties for"), - QMessageBox::Ok); - reject(); - return; + connect(ocio_filename_, &QLineEdit::textChanged, this, &ProjectPropertiesDialog::OCIOFilenameUpdated); + OCIOFilenameUpdated(); } - ocio_filename_->setText(working_project_->color_manager()->GetConfigFilename()); + { + // Paths group + QGroupBox* paths_group = new QGroupBox(); + paths_group->setTitle(tr("Paths")); - connect(ocio_filename_, &QLineEdit::textChanged, this, &ProjectPropertiesDialog::FilenameUpdated); - FilenameUpdated(); + QGridLayout* paths_layout = new QGridLayout(paths_group); + + cache_path_ = new PathWidget(working_project_->cache_path(), this); + proxy_path_ = new PathWidget(working_project_->proxy_path(), this); + + int row = 0; + + paths_layout->addWidget(new QLabel(tr("Cache Path:")), row, 0); + paths_layout->addWidget(cache_path_->path_edit(), row, 1); + paths_layout->addWidget(cache_path_->browse_btn(), row, 2); + paths_layout->addWidget(cache_path_->default_box(), row, 3); + + row++; + + paths_layout->addWidget(new QLabel(tr("Proxy Path:")), row, 0); + paths_layout->addWidget(proxy_path_->path_edit(), row, 1); + paths_layout->addWidget(proxy_path_->browse_btn(), row, 2); + paths_layout->addWidget(proxy_path_->default_box(), row, 3); + + layout->addWidget(paths_group); + } + + QDialogButtonBox* dialog_btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, + Qt::Horizontal); + layout->addWidget(dialog_btns); + connect(dialog_btns, &QDialogButtonBox::accepted, this, &ProjectPropertiesDialog::accept); + connect(dialog_btns, &QDialogButtonBox::rejected, this, &ProjectPropertiesDialog::reject); } void ProjectPropertiesDialog::accept() { - if (ocio_config_is_valid_) { - // This should ripple changes throughout the program that the color config has changed, therefore must be done last - working_project_->color_manager()->SetConfigAndDefaultInput(ocio_filename_->text(), default_input_colorspace_->currentText()); - - QDialog::accept(); - } else { - QMessageBox::critical(this, - tr("OpenColorIO Config Error"), - tr("Failed to set OpenColorIO configuration: %1").arg(ocio_config_error_), - QMessageBox::Ok); + if (!ocio_config_is_valid_) { + QMessageBox mb(this); + mb.setWindowModality(Qt::WindowModal); + mb.setIcon(QMessageBox::Critical); + mb.setWindowTitle(tr("OpenColorIO Config Error")); + mb.setText(tr("Failed to set OpenColorIO configuration: %1").arg(ocio_config_error_)); + mb.addButton(QMessageBox::Ok); + mb.exec(); + return; } + + if (!cache_path_->PathIsValid(true)) { + QMessageBox mb(this); + mb.setWindowModality(Qt::WindowModal); + mb.setIcon(QMessageBox::Critical); + mb.setWindowTitle(tr("Invalid path")); + mb.setText(tr("The cache path is invalid. Please check it and try again.")); + mb.addButton(QMessageBox::Ok); + mb.exec(); + return; + } + + if (!proxy_path_->PathIsValid(true)) { + QMessageBox mb(this); + mb.setWindowModality(Qt::WindowModal); + mb.setIcon(QMessageBox::Critical); + mb.setWindowTitle(tr("Invalid path")); + mb.setText(tr("The proxy path is invalid. Please check it and try again.")); + mb.addButton(QMessageBox::Ok); + mb.exec(); + return; + } + + working_project_->set_cache_path(cache_path_->path_edit()->text()); + working_project_->set_proxy_path(proxy_path_->path_edit()->text()); + + // This should ripple changes throughout the program that the color config has changed, therefore must be done last + working_project_->color_manager()->SetConfigAndDefaultInput(ocio_filename_->text(), + default_input_colorspace_->currentText()); + + QDialog::accept(); } void ProjectPropertiesDialog::BrowseForOCIOConfig() @@ -116,7 +167,7 @@ void ProjectPropertiesDialog::BrowseForOCIOConfig() } } -void ProjectPropertiesDialog::FilenameUpdated() +void ProjectPropertiesDialog::OCIOFilenameUpdated() { default_input_colorspace_->clear(); @@ -150,4 +201,54 @@ void ProjectPropertiesDialog::FilenameUpdated() } } +PathWidget::PathWidget(const QString &path, QWidget *parent) : + QObject(parent) +{ + path_edit_ = new QLineEdit(); + path_edit_->setText(path); + connect(path_edit_, &QLineEdit::textChanged, this, &PathWidget::LineEditChanged); + + default_box_ = new QCheckBox(tr("Default")); + + browse_btn_ = new QPushButton(tr("Browse")); + + connect(default_box_, &QCheckBox::toggled, this, &PathWidget::DefaultToggled); + + default_box_->setChecked(path.isEmpty()); + + connect(browse_btn_, &QPushButton::clicked, this, &PathWidget::BrowseClicked); +} + +bool PathWidget::PathIsValid(bool try_to_create) const +{ + return default_box_->isChecked() + || QDir(path_edit_->text()).exists() + || (try_to_create && QDir(path_edit_->text()).mkpath(QStringLiteral("."))); +} + +void PathWidget::DefaultToggled(bool e) +{ + path_edit_->setEnabled(!e); +} + +void PathWidget::BrowseClicked() +{ + QString dir = QFileDialog::getExistingDirectory(static_cast(parent()), + tr("Browse for path"), + path_edit_->text()); + + if (!dir.isEmpty()) { + path_edit_->setText(dir); + } +} + +void PathWidget::LineEditChanged() +{ + if (PathIsValid(false)) { + path_edit_->setStyleSheet(QString()); + } else { + path_edit_->setStyleSheet(QStringLiteral("QLineEdit {color: red;}")); + } +} + OLIVE_NAMESPACE_EXIT diff --git a/app/dialog/projectproperties/projectproperties.h b/app/dialog/projectproperties/projectproperties.h index 1fed66c6b..b7bafa012 100644 --- a/app/dialog/projectproperties/projectproperties.h +++ b/app/dialog/projectproperties/projectproperties.h @@ -1,59 +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 . - + ***/ #ifndef PROJECTPROPERTIESDIALOG_H #define PROJECTPROPERTIESDIALOG_H +#include #include #include +#include #include #include "project/project.h" OLIVE_NAMESPACE_ENTER +class PathWidget : public QObject +{ + Q_OBJECT +public: + PathWidget(const QString& path, + QWidget* parent = nullptr); + + bool PathIsValid(bool try_to_create) const; + + QLineEdit* path_edit() const { + return path_edit_; + } + + QCheckBox* default_box() const { + return default_box_; + } + + QPushButton* browse_btn() const { + return browse_btn_; + } + +private slots: + void DefaultToggled(bool e); + + void BrowseClicked(); + + void LineEditChanged(); + +private: + QLineEdit* path_edit_; + + QCheckBox* default_box_; + + QPushButton* browse_btn_; + +}; + class ProjectPropertiesDialog : public QDialog { Q_OBJECT public: ProjectPropertiesDialog(Project *p, QWidget* parent); - + public slots: virtual void accept() override; - + private: Project* working_project_; - + QLineEdit* ocio_filename_; - + QComboBox* default_input_colorspace_; - + bool ocio_config_is_valid_; - + QString ocio_config_error_; + PathWidget* cache_path_; + + PathWidget* proxy_path_; + private slots: void BrowseForOCIOConfig(); - - void FilenameUpdated(); - + + void OCIOFilenameUpdated(); + }; OLIVE_NAMESPACE_EXIT diff --git a/app/project/item/footage/audiostream.cpp b/app/project/item/footage/audiostream.cpp index 1ddfbf95c..6fb93267a 100644 --- a/app/project/item/footage/audiostream.cpp +++ b/app/project/item/footage/audiostream.cpp @@ -67,7 +67,7 @@ void AudioStream::set_sample_rate(const int &sample_rate) const rational &AudioStream::index_length() { - QMutexLocker locker(&index_access_lock_); + QMutexLocker locker(index_access_lock()); return index_length_; } @@ -75,7 +75,7 @@ const rational &AudioStream::index_length() void AudioStream::set_index_length(const rational &index_length) { { - QMutexLocker locker(&index_access_lock_); + QMutexLocker locker(index_access_lock()); index_length_ = index_length; } @@ -85,7 +85,7 @@ void AudioStream::set_index_length(const rational &index_length) const bool &AudioStream::index_done() { - QMutexLocker locker(&index_access_lock_); + QMutexLocker locker(index_access_lock()); return index_done_; } @@ -93,7 +93,7 @@ const bool &AudioStream::index_done() void AudioStream::set_index_done(const bool& index_done) { { - QMutexLocker locker(&index_access_lock_); + QMutexLocker locker(index_access_lock()); index_done_ = index_done; } @@ -103,7 +103,7 @@ void AudioStream::set_index_done(const bool& index_done) void AudioStream::clear_index() { - QMutexLocker locker(&index_access_lock_); + QMutexLocker locker(index_access_lock()); index_done_ = false; index_length_ = 0; @@ -111,7 +111,7 @@ void AudioStream::clear_index() bool AudioStream::has_conformed_version(const AudioRenderingParams ¶ms) { - QMutexLocker locker(&index_access_lock_); + QMutexLocker locker(index_access_lock()); foreach (const AudioRenderingParams& p, conformed_) { if (p == params) { @@ -125,7 +125,7 @@ bool AudioStream::has_conformed_version(const AudioRenderingParams ¶ms) void AudioStream::append_conformed_version(const AudioRenderingParams ¶ms) { { - QMutexLocker locker(&index_access_lock_); + QMutexLocker locker(index_access_lock()); conformed_.append(params); } diff --git a/app/project/item/footage/audiostream.h b/app/project/item/footage/audiostream.h index 1c5a6c4a5..b3a432309 100644 --- a/app/project/item/footage/audiostream.h +++ b/app/project/item/footage/audiostream.h @@ -68,7 +68,6 @@ private: uint64_t layout_; int sample_rate_; - QMutex index_access_lock_; rational index_length_; bool index_done_; diff --git a/app/project/item/footage/stream.cpp b/app/project/item/footage/stream.cpp index e0fe629f5..6ee029a71 100644 --- a/app/project/item/footage/stream.cpp +++ b/app/project/item/footage/stream.cpp @@ -139,16 +139,16 @@ QIcon Stream::IconFromType(const Stream::Type &type) return QIcon(); } -/*StreamID Stream::ToID() const -{ - return StreamID(footage_->filename(), index_); -}*/ - QMutex* Stream::index_process_lock() { return &index_process_lock_; } +QMutex *Stream::index_access_lock() +{ + return &index_access_lock_; +} + void Stream::FootageSetEvent(Footage*) { } @@ -162,10 +162,4 @@ void Stream::SaveCustomParameters(QXmlStreamWriter*) const { } -/*StreamID::StreamID(const QString &filename, const int &stream_index) : - filename_(filename), - stream_index_(stream_index) -{ -}*/ - OLIVE_NAMESPACE_EXIT diff --git a/app/project/item/footage/stream.h b/app/project/item/footage/stream.h index 4ba139811..663b15eba 100644 --- a/app/project/item/footage/stream.h +++ b/app/project/item/footage/stream.h @@ -33,17 +33,6 @@ OLIVE_NAMESPACE_ENTER class Footage; -/*class StreamID { -public: - StreamID(const QString& filename, const int& stream_index); - -private: - QString filename_; - - int stream_index_; - -};*/ - /** * @brief A base class for keeping metadata about a media stream. * @@ -103,9 +92,8 @@ public: static QIcon IconFromType(const Type& type); - //StreamID ToID() const; - QMutex* index_process_lock(); + QMutex* index_access_lock(); protected: virtual void FootageSetEvent(Footage*); @@ -134,6 +122,8 @@ private: QMutex index_process_lock_; + QMutex index_access_lock_; + }; using StreamPtr = std::shared_ptr; diff --git a/app/project/item/footage/videostream.cpp b/app/project/item/footage/videostream.cpp index 8bbd09125..a20899362 100644 --- a/app/project/item/footage/videostream.cpp +++ b/app/project/item/footage/videostream.cpp @@ -73,6 +73,21 @@ void VideoStream::set_image_sequence(bool e) is_image_sequence_ = e; } +bool VideoStream::has_proxy(const int ÷r) +{ + QMutexLocker locker(index_access_lock()); + + return proxies_.contains(divider); +} + +void VideoStream::append_proxy(const int ÷r) +{ + QMutexLocker locker(index_access_lock()); + + proxies_.append(divider); +} + +/* int64_t VideoStream::get_closest_timestamp_in_frame_index(const rational &time) { // Get rough approximation of what the timestamp would be in this timebase @@ -204,5 +219,6 @@ bool VideoStream::save_frame_index(const QString &s) return false; } +*/ OLIVE_NAMESPACE_EXIT diff --git a/app/project/item/footage/videostream.h b/app/project/item/footage/videostream.h index 8ce99a688..721df3277 100644 --- a/app/project/item/footage/videostream.h +++ b/app/project/item/footage/videostream.h @@ -49,6 +49,7 @@ 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(); @@ -58,6 +59,10 @@ public: bool load_frame_index(const QString& s); bool save_frame_index(const QString& s); + */ + + bool has_proxy(const int& divider); + void append_proxy(const int& divider); private: rational frame_rate_; @@ -66,10 +71,10 @@ private: int64_t start_time_; - QMutex index_access_lock_; - bool is_image_sequence_; + QVector proxies_; + }; using VideoStreamPtr = std::shared_ptr; diff --git a/app/project/project.h b/app/project/project.h index 5eb7123b9..09c898ff2 100644 --- a/app/project/project.h +++ b/app/project/project.h @@ -70,6 +70,22 @@ public: bool is_new() const; + const QString& cache_path() const { + return cache_path_; + } + + void set_cache_path(const QString& cache_path) { + cache_path_ = cache_path; + } + + const QString& proxy_path() const { + return proxy_path_; + } + + void set_proxy_path(const QString& proxy_path) { + proxy_path_ = proxy_path; + } + signals: void NameChanged(); @@ -86,6 +102,10 @@ private: bool autorecovery_saved_; + QString cache_path_; + + QString proxy_path_; + }; using ProjectPtr = std::shared_ptr; From e4c3b6bf7bce5c269f1843c9b69099939d360545 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sun, 3 May 2020 16:07:10 +1000 Subject: [PATCH 07/15] 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 From 6754feb2f6f1f3959c06aa7ff51d118a8a982f21 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sun, 3 May 2020 17:09:52 +1000 Subject: [PATCH 08/15] proxy: began work towards creating proxy from video --- app/codec/decoder.cpp | 2 +- app/codec/decoder.h | 2 +- app/codec/ffmpeg/ffmpegdecoder.cpp | 190 +++++++++++++----- app/project/item/footage/footage.cpp | 17 +- app/project/item/footage/footage.h | 16 +- app/project/item/footage/stream.h | 2 - app/project/item/footage/videostream.cpp | 33 ++- app/project/item/footage/videostream.h | 10 +- app/render/backend/audiorenderbackend.cpp | 2 + app/task/CMakeLists.txt | 1 + app/task/conform/conform.cpp | 10 +- app/task/proxy/CMakeLists.txt | 22 ++ app/task/proxy/proxy.cpp | 60 ++++++ app/task/proxy/proxy.h | 46 +++++ .../projectexplorer/projectexplorer.cpp | 54 +++++ app/widget/projectexplorer/projectexplorer.h | 2 + 16 files changed, 388 insertions(+), 81 deletions(-) create mode 100644 app/task/proxy/CMakeLists.txt create mode 100644 app/task/proxy/proxy.cpp create mode 100644 app/task/proxy/proxy.h diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index 239822742..cd3554872 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -343,7 +343,7 @@ bool Decoder::HasConformedVersion(const AudioRenderingParams ¶ms) return index_already_matches; } -void Decoder::SignalIndexProgress(const int64_t &ts) +void Decoder::SignalProcessingProgress(const int64_t &ts) { if (stream()->duration() != AV_NOPTS_VALUE && stream()->duration() != 0) { emit IndexProgress(qRound(100.0 * static_cast(ts) / static_cast(stream()->duration()))); diff --git a/app/codec/decoder.h b/app/codec/decoder.h index 5ab6b0c9c..7f273f936 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -245,7 +245,7 @@ signals: void IndexProgress(int); protected: - void SignalIndexProgress(const int64_t& ts); + void SignalProcessingProgress(const int64_t& ts); /** * @brief Returns the filename for the index diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index c8139d0d4..96e4a6ad9 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -27,6 +27,7 @@ extern "C" { #include } +#include #include #include #include @@ -588,8 +589,6 @@ void FFmpegDecoder::Error(const QString &s) bool FFmpegDecoder::ProxyVideo(const QAtomicInt *cancelled, int divider) { - return false; - VideoStreamPtr video_stream = std::static_pointer_cast(stream()); QString frame_index_file = GetIndexFilename().append('d').append(QString::number(divider)); @@ -597,32 +596,113 @@ bool FFmpegDecoder::ProxyVideo(const QAtomicInt *cancelled, int divider) if (QFileInfo::exists(frame_index_file)) { // A proxy of this type already exists so we can do nothing - video_stream->append_proxy(divider); - - } else { - - // Iterate each frame and transcode it to EXR - FFmpegDecoderInstance instance(stream()->footage()->filename().toUtf8(), stream()->index()); - - int ret; - - AVPacket* pkt = av_packet_alloc(); - AVFrame* frame = av_frame_alloc(); - - while (true) { - ret = instance.GetFrame(pkt, frame); - - if (ret < 0) { - if (ret == AVERROR_EOF) { - - } - } - } - - av_frame_free(&frame); - av_packet_free(&pkt); + video_stream->set_proxy(divider); + return true; } + + // Iterate each frame and transcode it to EXR + FFmpegDecoderInstance instance(stream()->footage()->filename().toUtf8(), stream()->index()); + + int ret; + + AVPixelFormat src_fmt = static_cast(instance.stream()->codecpar->format); + 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; + + SwsContext* scaler = sws_getContext(instance.stream()->codecpar->width, + instance.stream()->codecpar->height, + src_fmt, + divided_width, + divided_height, + ideal_fmt, + SWS_FAST_BILINEAR, + nullptr, + nullptr, + 0); + + AVPacket* pkt = av_packet_alloc(); + AVFrame* frame = av_frame_alloc(); + QVector frame_index; + + 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); + + bool succeeded = false; + + while (true) { + if (cancelled && *cancelled) { + break; + } + + ret = instance.GetFrame(pkt, frame); + + // Handle errors + if (ret < 0) { + if (ret == AVERROR_EOF) { + succeeded = true; + } else { + char err_str[50]; + av_strerror(ret, err_str, 50); + qWarning() << "Failed to proxy:" << ret << err_str; + } + + 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); + } + + sws_freeContext(scaler); + + av_frame_free(&frame); + av_packet_free(&pkt); + + return succeeded; } bool FFmpegDecoder::ConformAudio(const QAtomicInt *cancelled, const AudioRenderingParams &p) @@ -694,39 +774,39 @@ bool FFmpegDecoder::ConformAudio(const QAtomicInt *cancelled, const AudioRenderi } else { char err_str[50]; av_strerror(ret, err_str, 50); - qWarning() << "Failed to index:" << ret << err_str; + qWarning() << "Failed to conform:" << 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); } + + // 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; + } + + SignalProcessingProgress(frame->pts); } wave_out.close(); diff --git a/app/project/item/footage/footage.cpp b/app/project/item/footage/footage.cpp index 436f38516..4acb16569 100644 --- a/app/project/item/footage/footage.cpp +++ b/app/project/item/footage/footage.cpp @@ -304,11 +304,11 @@ void Footage::ClearStreams() streams_.clear(); } -bool Footage::HasStreamsOfType(const Stream::Type type) +bool Footage::HasStreamsOfType(const Stream::Type &type) const { // Return true if any streams are video streams - for (int i=0;itype() == type) { + foreach (StreamPtr stream, streams_) { + if (stream->type() == type) { return true; } } @@ -316,6 +316,17 @@ bool Footage::HasStreamsOfType(const Stream::Type type) return false; } +StreamPtr Footage::get_first_stream_of_type(const Stream::Type &type) const +{ + foreach (StreamPtr stream, streams_) { + if (stream->type() == type) { + return stream; + } + } + + return nullptr; +} + void Footage::UpdateTooltip() { switch (status_) { diff --git a/app/project/item/footage/footage.h b/app/project/item/footage/footage.h index a3a026508..2bcf485a5 100644 --- a/app/project/item/footage/footage.h +++ b/app/project/item/footage/footage.h @@ -206,12 +206,6 @@ public: quint64 get_enabled_stream_flags() const; -private: - /** - * @brief Internal function to delete all Stream children and empty the array - */ - void ClearStreams(); - /** * @brief Check if this footage has streams of a certain type * @@ -219,7 +213,15 @@ private: * * The stream type to check for */ - bool HasStreamsOfType(const Stream::Type type); + bool HasStreamsOfType(const Stream::Type& type) const; + + StreamPtr get_first_stream_of_type(const Stream::Type& type) const; + +private: + /** + * @brief Internal function to delete all Stream children and empty the array + */ + void ClearStreams(); /** * @brief Update the icon based on the Footage status diff --git a/app/project/item/footage/stream.h b/app/project/item/footage/stream.h index 62e942207..240f01b36 100644 --- a/app/project/item/footage/stream.h +++ b/app/project/item/footage/stream.h @@ -102,8 +102,6 @@ protected: virtual void SaveCustomParameters(QXmlStreamWriter* writer) const; signals: - void IndexChanged(); - void ParametersChanged(); private: diff --git a/app/project/item/footage/videostream.cpp b/app/project/item/footage/videostream.cpp index 0a90fd46a..25e67f586 100644 --- a/app/project/item/footage/videostream.cpp +++ b/app/project/item/footage/videostream.cpp @@ -30,7 +30,9 @@ const int64_t VideoStream::kEndTimestamp = AV_NOPTS_VALUE; VideoStream::VideoStream() : start_time_(0), - is_image_sequence_(false) + is_image_sequence_(false), + is_generating_proxy_(false), + using_proxy_(0) { set_type(kVideo); } @@ -73,18 +75,39 @@ void VideoStream::set_image_sequence(bool e) is_image_sequence_ = e; } -bool VideoStream::has_proxy(const int ÷r) +bool VideoStream::is_generating_proxy() { QMutexLocker locker(proxy_access_lock()); - return proxies_.contains(divider); + return is_generating_proxy_; } -void VideoStream::append_proxy(const int ÷r) +bool VideoStream::try_start_proxy() { QMutexLocker locker(proxy_access_lock()); - proxies_.append(divider); + if (is_generating_proxy_) { + return false; + } + + is_generating_proxy_ = true; + + return true; +} + +int VideoStream::using_proxy() +{ + QMutexLocker locker(proxy_access_lock()); + + return using_proxy_; +} + +void VideoStream::set_proxy(const int ÷r) +{ + QMutexLocker locker(proxy_access_lock()); + + using_proxy_ = divider; + is_generating_proxy_ = false; } /* diff --git a/app/project/item/footage/videostream.h b/app/project/item/footage/videostream.h index 721df3277..f50093af2 100644 --- a/app/project/item/footage/videostream.h +++ b/app/project/item/footage/videostream.h @@ -61,8 +61,10 @@ public: bool save_frame_index(const QString& s); */ - bool has_proxy(const int& divider); - void append_proxy(const int& divider); + bool is_generating_proxy(); + bool try_start_proxy(); + int using_proxy(); + void set_proxy(const int& divider); private: rational frame_rate_; @@ -73,7 +75,9 @@ private: bool is_image_sequence_; - QVector proxies_; + bool is_generating_proxy_; + + int using_proxy_; }; diff --git a/app/render/backend/audiorenderbackend.cpp b/app/render/backend/audiorenderbackend.cpp index cb48ebb4d..9e819c0c8 100644 --- a/app/render/backend/audiorenderbackend.cpp +++ b/app/render/backend/audiorenderbackend.cpp @@ -65,6 +65,8 @@ void AudioRenderBackend::DisconnectViewer(ViewerOutput *node) { disconnect(node, &ViewerOutput::AudioChangedBetween, this, &AudioRenderBackend::InvalidateCache); disconnect(node, &ViewerOutput::LengthChanged, this, &AudioRenderBackend::TruncateCache); + + conform_wait_info_.clear(); } bool AudioRenderBackend::GenerateCacheIDInternal(QCryptographicHash &hash) diff --git a/app/task/CMakeLists.txt b/app/task/CMakeLists.txt index 914998798..e9d4fa410 100644 --- a/app/task/CMakeLists.txt +++ b/app/task/CMakeLists.txt @@ -15,6 +15,7 @@ # along with this program. If not, see . add_subdirectory(conform) +add_subdirectory(proxy) set(OLIVE_SOURCES ${OLIVE_SOURCES} diff --git a/app/task/conform/conform.cpp b/app/task/conform/conform.cpp index 7202c572b..091c85e35 100644 --- a/app/task/conform/conform.cpp +++ b/app/task/conform/conform.cpp @@ -34,7 +34,7 @@ ConformTask::ConformTask(AudioStreamPtr stream, const AudioRenderingParams& para void ConformTask::Action() { if (stream_->footage()->decoder().isEmpty()) { - emit Failed(QStringLiteral("Stream has no decoder")); + emit Failed(tr("Failed to find decoder to conform audio stream")); } else { DecoderPtr decoder = Decoder::CreateFromID(stream_->footage()->decoder()); @@ -42,9 +42,11 @@ void ConformTask::Action() connect(decoder.get(), &Decoder::IndexProgress, this, &ConformTask::ProgressChanged); - decoder->ConformAudio(&IsCancelled(), params_); - - emit Succeeded(); + if (decoder->ConformAudio(&IsCancelled(), params_)) { + emit Succeeded(); + } else { + emit Failed(QStringLiteral("Failed to conform audio")); + } } } diff --git a/app/task/proxy/CMakeLists.txt b/app/task/proxy/CMakeLists.txt new file mode 100644 index 000000000..6d7ca02aa --- /dev/null +++ b/app/task/proxy/CMakeLists.txt @@ -0,0 +1,22 @@ +# 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/proxy/proxy.h + task/proxy/proxy.cpp + PARENT_SCOPE +) diff --git a/app/task/proxy/proxy.cpp b/app/task/proxy/proxy.cpp new file mode 100644 index 000000000..f086508c7 --- /dev/null +++ b/app/task/proxy/proxy.cpp @@ -0,0 +1,60 @@ +/*** + + 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 "proxy.h" + +#include "codec/decoder.h" + +OLIVE_NAMESPACE_ENTER + +ProxyTask::ProxyTask(VideoStreamPtr stream, int divider) : + stream_(stream), + divider_(divider) +{ + if (divider_ == 1) { + SetTitle(tr("Generating full resolution proxy %1:%2").arg(stream_->footage()->filename(), + QString::number(stream_->index()))); + } else { + SetTitle(tr("Generating 1/%1 resolution proxy %2:%3").arg(QString::number(divider), + stream_->footage()->filename(), + QString::number(stream_->index()))); + } +} + +void ProxyTask::Action() +{ + if (stream_->footage()->decoder().isEmpty()) { + emit Failed(tr("Failed to find decoder to conform audio stream")); + } else { + DecoderPtr decoder = Decoder::CreateFromID(stream_->footage()->decoder()); + + decoder->set_stream(stream_); + + connect(decoder.get(), &Decoder::IndexProgress, this, &ProxyTask::ProgressChanged); + + if (decoder->ProxyVideo(&IsCancelled(), divider_)) { + emit Succeeded(); + } else { + emit Failed(QStringLiteral("Failed to generate proxy")); + } + } +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/task/proxy/proxy.h b/app/task/proxy/proxy.h new file mode 100644 index 000000000..10610f074 --- /dev/null +++ b/app/task/proxy/proxy.h @@ -0,0 +1,46 @@ +/*** + + 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 PROXYTASK_H +#define PROXYTASK_H + +#include "project/item/footage/videostream.h" +#include "task/task.h" + +OLIVE_NAMESPACE_ENTER + +class ProxyTask : public Task +{ +public: + ProxyTask(VideoStreamPtr stream, int divider); + +protected: + virtual void Action() override; + +private: + VideoStreamPtr stream_; + + int divider_; + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // PROXYTASK_H diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 29fb5fbbc..ae86d1092 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -31,6 +31,8 @@ #include "core.h" #include "dialog/footageproperties/footageproperties.h" #include "dialog/sequence/sequence.h" +#include "task/proxy/proxy.h" +#include "task/taskmanager.h" #include "widget/menu/menu.h" #include "widget/menu/menushared.h" #include "window/mainwindow/mainwindow.h" @@ -284,6 +286,36 @@ void ProjectExplorer::ShowContextMenu() connect(reveal_action, &QAction::triggered, this, &ProjectExplorer::RevealSelectedFootage); menu.addSeparator(); + + Footage* f = static_cast(context_menu_item_); + + if (f->HasStreamsOfType(Stream::kVideo)) { + Menu* proxy_menu = new Menu(tr("Proxy"), &menu); + menu.addMenu(proxy_menu); + + VideoStreamPtr video_stream = std::static_pointer_cast(f->get_first_stream_of_type(Stream::kVideo)); + + if (video_stream->is_generating_proxy()) { + + // Prevent multiple proxy actions from occurring at once + QAction* cant_proxy_action = proxy_menu->addAction(tr("Proxy being generated...")); + cant_proxy_action->setEnabled(false); + + } else { + + proxy_menu->addAction(tr("(None)"))->setData(0); + proxy_menu->addSeparator(); + proxy_menu->addAction(tr("Full"))->setData(1); + proxy_menu->addAction(tr("1/2"))->setData(2); + proxy_menu->addAction(tr("1/4"))->setData(4); + proxy_menu->addAction(tr("1/8"))->setData(8); + + connect(proxy_menu, &Menu::triggered, this, &ProjectExplorer::ContextMenuStartProxy); + + } + + menu.addSeparator(); + } } QAction* properties_action = menu.addAction(tr("P&roperties")); @@ -347,6 +379,28 @@ void ProjectExplorer::OpenContextMenuItemInNewWindow() Core::instance()->main_window()->FolderOpen(project(), context_menu_item_, true); } +void ProjectExplorer::ContextMenuStartProxy(QAction *a) +{ + // Find video stream + VideoStreamPtr video_stream = nullptr; + + foreach (StreamPtr s, static_cast(context_menu_item_)->streams()) { + if (s->type() == Stream::kVideo) { + video_stream = std::static_pointer_cast(s); + break; + } + } + + if (!video_stream) { + return; + } + + if (video_stream->try_start_proxy()) { + ProxyTask* proxy_task = new ProxyTask(video_stream, a->data().toInt()); + TaskManager::instance()->AddTask(proxy_task); + } +} + Project *ProjectExplorer::project() const { return model_.project(); diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index b5219b84d..69bce4b49 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -171,6 +171,8 @@ private slots: void OpenContextMenuItemInNewWindow(); + void ContextMenuStartProxy(QAction* a); + }; OLIVE_NAMESPACE_EXIT From b2acb5c326c0fc86386ee88d31bee0c1d20ddca6 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sun, 3 May 2020 17:15:09 +1000 Subject: [PATCH 09/15] style: use native menu styling on macOS too --- app/ui/style/style.cpp | 10 +++++++--- app/ui/style/style.h | 4 +--- app/widget/menu/menu.cpp | 4 +--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/ui/style/style.cpp b/app/ui/style/style.cpp index 832202450..06753432e 100644 --- a/app/ui/style/style.cpp +++ b/app/ui/style/style.cpp @@ -45,14 +45,18 @@ QList StyleManager::ListInternal() return style_list; } -#ifdef Q_OS_WINDOWS -void StyleManager::UseNativeWindowsStyling(QWidget *widget) +void StyleManager::UseOSNativeStyling(QWidget *widget) { +#if defined(Q_OS_WINDOWS) QStyle* s = QStyleFactory::create(QStringLiteral("windowsvista")); widget->setStyle(s); widget->setPalette(s->standardPalette()); -} +#elif defined(Q_OS_MAC) + QStyle* s = QStyleFactory::create(QStringLiteral("macintosh")); + widget->setStyle(s); + widget->setPalette(s->standardPalette()); #endif +} QPalette StyleManager::ParsePalette(const QString& ini_path) { diff --git a/app/ui/style/style.h b/app/ui/style/style.h index 77f980263..23dbc4e9c 100644 --- a/app/ui/style/style.h +++ b/app/ui/style/style.h @@ -54,9 +54,7 @@ public: static QList ListInternal(); -#ifdef Q_OS_WINDOWS - static void UseNativeWindowsStyling(QWidget* widget); -#endif + static void UseOSNativeStyling(QWidget* widget); private: static QPalette ParsePalette(const QString& ini_path); diff --git a/app/widget/menu/menu.cpp b/app/widget/menu/menu.cpp index 5515c65da..b744c3d32 100644 --- a/app/widget/menu/menu.cpp +++ b/app/widget/menu/menu.cpp @@ -110,9 +110,7 @@ void Menu::SetBooleanAction(QAction *a, bool* boolean) void Menu::Init() { -#ifdef Q_OS_WINDOWS - StyleManager::UseNativeWindowsStyling(this); -#endif + StyleManager::UseOSNativeStyling(this); } OLIVE_NAMESPACE_EXIT From e4e797c901b1caaa55a4c7e5b65a2477130ebf4a Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 4 May 2020 00:29:26 +1000 Subject: [PATCH 10/15] proxy: cleaned up, re-use the render cache's disk save system for proxies, multithreaded compression --- app/codec/decoder.cpp | 15 +- app/codec/decoder.h | 4 +- app/codec/ffmpeg/ffmpegdecoder.cpp | 197 ++++++++++++++----- app/codec/ffmpeg/ffmpegdecoder.h | 6 +- app/codec/oiio/oiiodecoder.cpp | 2 +- app/codec/oiio/oiiodecoder.h | 2 +- app/project/item/footage/videostream.cpp | 56 ++---- app/project/item/footage/videostream.h | 6 +- app/render/backend/videorenderbackend.cpp | 7 +- app/render/backend/videorenderbackend.h | 2 +- app/render/backend/videorenderframecache.cpp | 129 ++++++++++-- app/render/backend/videorenderframecache.h | 16 +- app/render/backend/videorenderworker.cpp | 98 +-------- app/render/backend/videorenderworker.h | 4 +- 14 files changed, 318 insertions(+), 226 deletions(-) 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(); From 2b1640ac04ebb543728fe8d0582e4ef294d48a01 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 4 May 2020 01:08:46 +1000 Subject: [PATCH 11/15] frame/texture: use dividers when allocating buffers Allows render pipeline to know the "real" and "simulated" resolution of a divided buffer. --- app/codec/decoder.cpp | 2 +- app/codec/decoder.h | 2 +- app/codec/ffmpeg/ffmpegdecoder.cpp | 16 ++++--- app/codec/ffmpeg/ffmpegdecoder.h | 2 +- app/codec/frame.cpp | 6 +-- app/codec/oiio/oiiodecoder.cpp | 2 +- app/codec/oiio/oiiodecoder.h | 2 +- app/render/backend/opengl/openglproxy.cpp | 28 +++++------ app/render/backend/opengl/opengltexture.cpp | 46 +++++++++---------- app/render/backend/opengl/opengltexture.h | 14 +++--- .../backend/opengl/opengltexturecache.cpp | 10 ++-- .../backend/opengl/opengltexturecache.h | 4 +- app/render/backend/opengl/openglworker.cpp | 4 +- app/widget/scope/waveform/waveform.cpp | 2 +- app/widget/viewer/viewerdisplay.cpp | 4 +- 15 files changed, 74 insertions(+), 70 deletions(-) diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index 2cccc4b1c..b224e5f99 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -57,7 +57,7 @@ void Decoder::set_stream(StreamPtr fs) stream_ = fs; } -FramePtr Decoder::RetrieveVideo(const rational &/*timecode*/, const int &/*divider*/) +FramePtr Decoder::RetrieveVideo(const rational &/*timecode*/, const int &/*divider*/, bool /*use_proxies*/) { return nullptr; } diff --git a/app/codec/decoder.h b/app/codec/decoder.h index 1f9179e96..d6adbe0c4 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -138,7 +138,7 @@ public: * 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. */ - virtual FramePtr RetrieveVideo(const rational& timecode, const int& divider); + virtual FramePtr RetrieveVideo(const rational& timecode, const int& divider, bool use_proxies); /** * @brief Retrieve video frame diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 17925d5c0..3c497adb5 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -135,7 +135,7 @@ bool FFmpegDecoder::Open() return true; } -FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode, const int ÷r) +FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode, const int ÷r, bool use_proxies) { QMutexLocker locker(&mutex_); @@ -169,9 +169,10 @@ FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode, const int &divid 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_video_params(VideoRenderingParams(vs->width(), + vs->height(), + native_pix_fmt_, + vs->using_proxy())); copy->set_timestamp(Timecode::timestamp_to_time(target_ts, time_base_)); copy->set_sample_aspect_ratio(aspect_ratio_); copy->allocate(); @@ -314,9 +315,10 @@ FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode, const int &divid // Create frame to return FramePtr copy = Frame::Create(); - copy->set_video_params(VideoRenderingParams(GetScaledDimension(vs->width(), divider), - GetScaledDimension(vs->height(), divider), - native_pix_fmt_)); + copy->set_video_params(VideoRenderingParams(vs->width(), + vs->height(), + native_pix_fmt_, + divider)); copy->set_timestamp(Timecode::timestamp_to_time(target_ts, time_base_)); copy->set_sample_aspect_ratio(aspect_ratio_); copy->allocate(); diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index f02e37f75..206b0ab5a 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -134,7 +134,7 @@ public: virtual bool Probe(Footage *f, const QAtomicInt *cancelled) override; virtual bool Open() override; - virtual FramePtr RetrieveVideo(const rational &timecode, const int& divider) override; + virtual FramePtr RetrieveVideo(const rational &timecode, const int& divider, bool use_proxies) override; virtual SampleBufferPtr RetrieveAudio(const rational &timecode, const rational &length, const AudioRenderingParams& params) override; virtual void Close() override; diff --git a/app/codec/frame.cpp b/app/codec/frame.cpp index bb18ace05..ec026d0df 100644 --- a/app/codec/frame.cpp +++ b/app/codec/frame.cpp @@ -47,7 +47,7 @@ void Frame::set_video_params(const VideoRenderingParams ¶ms) params_ = params; // Align linesize to 16 - linesize_ = qCeil(static_cast(params.width()) / 16.0) * 16; + linesize_ = qCeil(static_cast(width()) / 16.0) * 16; } int Frame::linesize_pixels() const @@ -62,12 +62,12 @@ int Frame::linesize_bytes() const const int &Frame::width() const { - return params_.width(); + return params_.effective_width(); } const int &Frame::height() const { - return params_.height(); + return params_.effective_height(); } const PixelFormat::Format &Frame::format() const diff --git a/app/codec/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp index 5a2eac463..27e02991f 100644 --- a/app/codec/oiio/oiiodecoder.cpp +++ b/app/codec/oiio/oiiodecoder.cpp @@ -154,7 +154,7 @@ bool OIIODecoder::Open() return true; } -FramePtr OIIODecoder::RetrieveVideo(const rational &timecode, const int& divider) +FramePtr OIIODecoder::RetrieveVideo(const rational &timecode, const int& divider, bool /*use_proxies*/) { QMutexLocker locker(&mutex_); diff --git a/app/codec/oiio/oiiodecoder.h b/app/codec/oiio/oiiodecoder.h index 2bc03d169..0412789a8 100644 --- a/app/codec/oiio/oiiodecoder.h +++ b/app/codec/oiio/oiiodecoder.h @@ -40,7 +40,7 @@ public: virtual bool Probe(Footage *f, const QAtomicInt* cancelled) override; virtual bool Open() override; - virtual FramePtr RetrieveVideo(const rational &timecode, const int& divider) override; + virtual FramePtr RetrieveVideo(const rational &timecode, const int& divider, bool use_proxies) override; virtual void Close() override; virtual bool SupportsVideo() override; diff --git a/app/render/backend/opengl/openglproxy.cpp b/app/render/backend/opengl/openglproxy.cpp index 194ac6dc4..05b4e5609 100644 --- a/app/render/backend/opengl/openglproxy.cpp +++ b/app/render/backend/opengl/openglproxy.cpp @@ -130,19 +130,19 @@ void OpenGLProxy::FrameToValue(FramePtr frame, StreamPtr stream, NodeValueTable* } } - VideoRenderingParams footage_params(frame->width(), frame->height(), frame->format()); - - footage_tex_ref = texture_cache_.Get(ctx_, footage_params, frame); + footage_tex_ref = texture_cache_.Get(ctx_, frame); if (ocio_method == ColorManager::kOCIOFast) { if (!color_processor->IsEnabled()) { color_processor->Enable(ctx_, video_stream->premultiplied_alpha()); } + VideoRenderingParams frame_params = frame->video_params(); + // Check frame aspect ratio if (frame->sample_aspect_ratio() != 1 && frame->sample_aspect_ratio() != 0) { - int new_width = frame->width(); - int new_height = frame->height(); + int new_width = frame_params.width(); + int new_height = frame_params.height(); // Scale the frame in a way that does not reduce the resolution if (frame->sample_aspect_ratio() > 1) { @@ -153,14 +153,16 @@ void OpenGLProxy::FrameToValue(FramePtr frame, StreamPtr stream, NodeValueTable* new_height = qRound(static_cast(new_height) / frame->sample_aspect_ratio().toDouble()); } - footage_params = VideoRenderingParams(new_width, - new_height, - footage_params.format()); + frame_params = VideoRenderingParams(new_width, + new_height, + frame_params.format(), + frame_params.divider()); } - VideoRenderingParams dest_params(footage_params.width(), - footage_params.height(), - video_params_.format()); + VideoRenderingParams dest_params(frame_params.width(), + frame_params.height(), + video_params_.format(), + frame_params.divider()); // Create destination texture OpenGLTextureCache::ReferencePtr associated_tex_ref = texture_cache_.Get(ctx_, dest_params); @@ -329,8 +331,8 @@ void OpenGLProxy::RunNodeAccelerated(const Node *node, const TimeRange &range, N int res_param_location = shader->uniformLocation(QStringLiteral("%1_resolution").arg(input->id())); if (res_param_location > -1) { shader->setUniformValue(res_param_location, - static_cast(texture->texture()->width() * video_params_.divider()), - static_cast(texture->texture()->height() * video_params_.divider())); + static_cast(texture->texture()->width() * texture->texture()->divider()), + static_cast(texture->texture()->height() * texture->texture()->divider())); } } diff --git a/app/render/backend/opengl/opengltexture.cpp b/app/render/backend/opengl/opengltexture.cpp index d5013c10f..021840fcb 100644 --- a/app/render/backend/opengl/opengltexture.cpp +++ b/app/render/backend/opengl/opengltexture.cpp @@ -31,10 +31,7 @@ OLIVE_NAMESPACE_ENTER OpenGLTexture::OpenGLTexture() : created_ctx_(nullptr), - texture_(0), - width_(0), - height_(0), - format_(PixelFormat::PIX_FMT_INVALID) + texture_(0) { } @@ -48,7 +45,7 @@ bool OpenGLTexture::IsCreated() const return (texture_); } -void OpenGLTexture::Create(QOpenGLContext *ctx, int width, int height, const PixelFormat::Format &format, const void* data, int linesize) +void OpenGLTexture::Create(QOpenGLContext *ctx, const VideoRenderingParams ¶ms, const void* data, int linesize) { if (!ctx) { qWarning() << "OpenGLTexture::Create was passed an invalid context"; @@ -58,9 +55,7 @@ void OpenGLTexture::Create(QOpenGLContext *ctx, int width, int height, const Pix Destroy(); created_ctx_ = ctx; - width_ = width; - height_ = height; - format_ = format; + params_ = params; connect(created_ctx_, SIGNAL(aboutToBeDestroyed()), this, SLOT(Destroy()), Qt::DirectConnection); @@ -68,9 +63,9 @@ void OpenGLTexture::Create(QOpenGLContext *ctx, int width, int height, const Pix CreateInternal(created_ctx_, &texture_, data, linesize); } -void OpenGLTexture::Create(QOpenGLContext *ctx, int width, int height, const PixelFormat::Format &format) +void OpenGLTexture::Create(QOpenGLContext *ctx, const VideoRenderingParams ¶ms) { - Create(ctx, width, height, format, nullptr, 0); + Create(ctx, params, nullptr, 0); } void OpenGLTexture::Create(QOpenGLContext *ctx, FramePtr frame) @@ -80,7 +75,7 @@ void OpenGLTexture::Create(QOpenGLContext *ctx, FramePtr frame) void OpenGLTexture::Create(QOpenGLContext *ctx, Frame *frame) { - Create(ctx, frame->width(), frame->height(), frame->format(), frame->data(), frame->linesize_pixels()); + Create(ctx, frame->video_params(), frame->data(), frame->linesize_pixels()); } void OpenGLTexture::Destroy() @@ -107,17 +102,17 @@ void OpenGLTexture::Release() const int &OpenGLTexture::width() const { - return width_; + return params_.effective_width(); } const int &OpenGLTexture::height() const { - return height_; + return params_.effective_height(); } const PixelFormat::Format &OpenGLTexture::format() const { - return format_; + return params_.format(); } const GLuint &OpenGLTexture::texture() const @@ -125,6 +120,11 @@ const GLuint &OpenGLTexture::texture() const return texture_; } +const int &OpenGLTexture::divider() const +{ + return params_.divider(); +} + void OpenGLTexture::Upload(FramePtr frame) { Upload(frame.get()); @@ -150,10 +150,10 @@ void OpenGLTexture::Upload(const void *data, int linesize) 0, 0, 0, - width_, - height_, - OpenGLRenderFunctions::GetPixelFormat(format_), - OpenGLRenderFunctions::GetPixelType(format_), + width(), + height(), + OpenGLRenderFunctions::GetPixelFormat(format()), + OpenGLRenderFunctions::GetPixelType(format()), data); created_ctx_->functions()->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); @@ -183,12 +183,12 @@ void OpenGLTexture::CreateInternal(QOpenGLContext* create_ctx, GLuint* tex, cons // Allocate storage for texture f->glTexImage2D(GL_TEXTURE_2D, 0, - OpenGLRenderFunctions::GetInternalFormat(format_), - width_, - height_, + OpenGLRenderFunctions::GetInternalFormat(format()), + width(), + height(), 0, - OpenGLRenderFunctions::GetPixelFormat(format_), - OpenGLRenderFunctions::GetPixelType(format_), + OpenGLRenderFunctions::GetPixelFormat(format()), + OpenGLRenderFunctions::GetPixelType(format()), data); // Return linesize to default diff --git a/app/render/backend/opengl/opengltexture.h b/app/render/backend/opengl/opengltexture.h index 70fa653f7..ef213526e 100644 --- a/app/render/backend/opengl/opengltexture.h +++ b/app/render/backend/opengl/opengltexture.h @@ -1,4 +1,4 @@ -/*** +/*** Olive - Non-Linear Video Editor Copyright (C) 2019 Olive Team @@ -41,8 +41,8 @@ public: DISABLE_COPY_MOVE(OpenGLTexture) - void Create(QOpenGLContext* ctx, int width, int height, const PixelFormat::Format &format, const void *data, int linesize); - void Create(QOpenGLContext* ctx, int width, int height, const PixelFormat::Format &format); + void Create(QOpenGLContext* ctx, const VideoRenderingParams& params, const void *data, int linesize); + void Create(QOpenGLContext* ctx, const VideoRenderingParams& params); void Create(QOpenGLContext* ctx, FramePtr frame); void Create(QOpenGLContext* ctx, Frame* frame); @@ -60,6 +60,8 @@ public: const GLuint& texture() const; + const int& divider() const; + void Upload(FramePtr frame); void Upload(Frame* frame); void Upload(const void *data, int linesize); @@ -74,11 +76,7 @@ private: GLuint texture_; - int width_; - - int height_; - - PixelFormat::Format format_; + VideoRenderingParams params_; }; diff --git a/app/render/backend/opengl/opengltexturecache.cpp b/app/render/backend/opengl/opengltexturecache.cpp index a54994467..5ca1fee23 100644 --- a/app/render/backend/opengl/opengltexturecache.cpp +++ b/app/render/backend/opengl/opengltexturecache.cpp @@ -29,14 +29,14 @@ OpenGLTextureCache::~OpenGLTextureCache() } } -OpenGLTextureCache::ReferencePtr OpenGLTextureCache::Get(QOpenGLContext *ctx, const VideoRenderingParams ¶ms, FramePtr frame) +OpenGLTextureCache::ReferencePtr OpenGLTextureCache::Get(QOpenGLContext *ctx, FramePtr frame) { - return Get(ctx, params, frame.get()); + return Get(ctx, frame.get()); } -OpenGLTextureCache::ReferencePtr OpenGLTextureCache::Get(QOpenGLContext *ctx, const VideoRenderingParams ¶ms, Frame *frame) +OpenGLTextureCache::ReferencePtr OpenGLTextureCache::Get(QOpenGLContext *ctx, Frame *frame) { - return Get(ctx, params, frame->data(), frame->linesize_pixels()); + return Get(ctx, frame->video_params(), frame->data(), frame->linesize_pixels()); } OpenGLTextureCache::ReferencePtr OpenGLTextureCache::Get(QOpenGLContext* ctx, const VideoRenderingParams ¶ms, const void *data, int linesize) @@ -61,7 +61,7 @@ OpenGLTextureCache::ReferencePtr OpenGLTextureCache::Get(QOpenGLContext* ctx, co // If we didn't find a texture, we'll need to create one if (!texture) { texture = std::make_shared(); - texture->Create(ctx, params.effective_width(), params.effective_height(), params.format()); + texture->Create(ctx, params); } ReferencePtr ref = std::make_shared(this, texture); diff --git a/app/render/backend/opengl/opengltexturecache.h b/app/render/backend/opengl/opengltexturecache.h index 2baa87a67..42c08dbb3 100644 --- a/app/render/backend/opengl/opengltexturecache.h +++ b/app/render/backend/opengl/opengltexturecache.h @@ -57,8 +57,8 @@ public: DISABLE_COPY_MOVE(OpenGLTextureCache) - ReferencePtr Get(QOpenGLContext *ctx, const VideoRenderingParams& params, FramePtr frame); - ReferencePtr Get(QOpenGLContext *ctx, const VideoRenderingParams& params, Frame* frame); + ReferencePtr Get(QOpenGLContext *ctx, FramePtr frame); + ReferencePtr Get(QOpenGLContext *ctx, Frame* frame); ReferencePtr Get(QOpenGLContext *ctx, const VideoRenderingParams& params, const void *data, int linesize); ReferencePtr Get(QOpenGLContext *ctx, const VideoRenderingParams& params); diff --git a/app/render/backend/opengl/openglworker.cpp b/app/render/backend/opengl/openglworker.cpp index 67f3fa835..aa31b9e32 100644 --- a/app/render/backend/opengl/openglworker.cpp +++ b/app/render/backend/opengl/openglworker.cpp @@ -38,7 +38,9 @@ OpenGLWorker::OpenGLWorker(VideoRenderFrameCache *frame_cache, QObject *parent) void OpenGLWorker::FrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeRange &range, NodeValueTable *table) { - FramePtr frame = decoder->RetrieveVideo(range.in(), video_params().divider()); + FramePtr frame = decoder->RetrieveVideo(range.in(), + video_params().divider(), + video_params().mode() == RenderMode::kOffline); if (frame) { emit RequestFrameToValue(frame, stream, table); diff --git a/app/widget/scope/waveform/waveform.cpp b/app/widget/scope/waveform/waveform.cpp index 211e66ef3..5f9225b3f 100644 --- a/app/widget/scope/waveform/waveform.cpp +++ b/app/widget/scope/waveform/waveform.cpp @@ -196,7 +196,7 @@ void WaveformScope::UploadTextureFromBuffer() managed_tex_.Destroy(); texture_.Create(context(), buffer_); - managed_tex_.Create(context(), buffer_->width(), buffer_->height(), buffer_->format()); + managed_tex_.Create(context(), buffer_->video_params()); } else { texture_.Upload(buffer_); } diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 779f65237..469fcd7aa 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -83,7 +83,7 @@ void ViewerDisplayWidget::SetImage(const QString &fn) load_buffer_.set_video_params(VideoRenderingParams(input->spec().width, input->spec().height, image_format)); load_buffer_.allocate(); - texture_.Create(context(), input->spec().width, input->spec().height, image_format); + texture_.Create(context(), VideoRenderingParams(input->spec().width, input->spec().height, image_format)); } input->read_image(input->spec().format, load_buffer_.data(), OIIO::AutoStride, load_buffer_.linesize_bytes()); @@ -132,7 +132,7 @@ void ViewerDisplayWidget::SetImageFromLoadBuffer(Frame *in_buffer) || texture_.width() != in_buffer->width() || texture_.height() != in_buffer->height() || texture_.format() != in_buffer->format()) { - texture_.Create(context(), in_buffer->width(), in_buffer->height(), in_buffer->format(), in_buffer->data(), load_buffer_.linesize_pixels()); + texture_.Create(context(), in_buffer->video_params(), in_buffer->data(), load_buffer_.linesize_pixels()); } else { texture_.Upload(in_buffer); } From b458bfa54259efd2f02395749134504a9f216e88 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 4 May 2020 01:33:26 +1000 Subject: [PATCH 12/15] proxy: improved UI on proxy menu --- app/codec/ffmpeg/ffmpegdecoder.cpp | 11 ++++---- app/render/backend/videorenderframecache.cpp | 6 +++++ .../projectexplorer/projectexplorer.cpp | 26 ++++++++++++++++--- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 3c497adb5..1f5e4d422 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -760,6 +760,12 @@ bool FFmpegDecoder::ProxyVideo(const QAtomicInt *cancelled, int divider) futures.append(future); } + // Wait for all conversions to finish + for (int i=0;iset_proxy(divider, frame_index); } - // Wait for all conversions to finish - for (int i=0;i &VideoRenderFrameCache::time_hash_map() const QString VideoRenderFrameCache::GetFormatExtension(const PixelFormat::Format &f) { if (PixelFormat::FormatIsFloat(f)) { + // EXR is only fast with float buffers so we only use it for those return QStringLiteral(".exr"); } else { + // FIXME: Will probably need different codec here. JPEG is the fastest and smallest by far (much + // more so than TIFF or PNG) and we don't mind lossy for the offline cache, but JPEG + // doesn't support >8-bit or alpha channels. JPEG2000 does, but my OIIO wasn't compiled + // with it and I imagine it's not common in general. Still, this works well for now as a + // prototype. return QStringLiteral(".jpg"); } } diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index ae86d1092..d6c109834 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -310,6 +310,14 @@ void ProjectExplorer::ShowContextMenu() proxy_menu->addAction(tr("1/4"))->setData(4); proxy_menu->addAction(tr("1/8"))->setData(8); + foreach (QAction* a, proxy_menu->actions()) { + a->setCheckable(true); + + if (a->data() == video_stream->using_proxy()) { + a->setChecked(true); + } + } + connect(proxy_menu, &Menu::triggered, this, &ProjectExplorer::ContextMenuStartProxy); } @@ -395,9 +403,21 @@ void ProjectExplorer::ContextMenuStartProxy(QAction *a) return; } - if (video_stream->try_start_proxy()) { - ProxyTask* proxy_task = new ProxyTask(video_stream, a->data().toInt()); - TaskManager::instance()->AddTask(proxy_task); + int chosen_proxy_setting = a->data().toInt(); + + if (chosen_proxy_setting != video_stream->using_proxy()) { + if (!a->data().toInt()) { + + // 0 means disable the proxy + video_stream->set_proxy(0, QVector()); + + } else if (video_stream->try_start_proxy()) { + + // Start a background task for proxying + ProxyTask* proxy_task = new ProxyTask(video_stream, a->data().toInt()); + TaskManager::instance()->AddTask(proxy_task); + + } } } From 22bfff2c62e0266cd285b5aed2bad2dfbe9efc6b Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 4 May 2020 01:53:47 +1000 Subject: [PATCH 13/15] proxy: improved progress signalling Signals once cache to disk is fully complete (rather than signalling on decode and then sitting at 100% for ages until the disk saving is done). --- app/codec/ffmpeg/ffmpegdecoder.cpp | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 1f5e4d422..abea45088 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -152,7 +152,7 @@ FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode, const int &divid VideoStreamPtr vs = std::static_pointer_cast(stream()); - if (vs->using_proxy()) { + if (use_proxies && vs->using_proxy()) { QString proxy_fn = GetProxyFilename(vs->using_proxy()); int64_t index_ts = vs->get_closest_timestamp_in_frame_index(target_ts); @@ -636,7 +636,8 @@ void FFmpegDecoder::Error(const QString &s) } QMutex scaler_lock; -void SaveCacheFrame(SwsContext* scaler, +void SaveCacheFrame(FFmpegDecoder* decoder, + SwsContext* scaler, AVFrame* frame, VideoRenderingParams params, QString dst_fn) @@ -719,6 +720,7 @@ bool FFmpegDecoder::ProxyVideo(const QAtomicInt *cancelled, int divider) AVPacket* pkt = av_packet_alloc(); QVector frame_index; QVector< QFuture > futures; + int finished_futures = 0; VideoRenderingParams converted_params(divided_width, divided_height, @@ -750,19 +752,29 @@ bool FFmpegDecoder::ProxyVideo(const QAtomicInt *cancelled, int divider) } frame_index.append(frame->pts); - SignalProcessingProgress(frame->pts); QFuture future = QtConcurrent::run(SaveCacheFrame, + this, scaler, frame, converted_params, GetProxyFrameFilename(frame->pts, divider)); futures.append(future); + + while (finished_futures < futures.size()) { + if (!futures.at(finished_futures).isFinished()) { + SignalProcessingProgress(frame_index.at(finished_futures)); + break; + } + + finished_futures++; + } } // Wait for all conversions to finish - for (int i=0;i Date: Mon, 4 May 2020 02:09:52 +1000 Subject: [PATCH 14/15] footageproperties: auto-select first usable stream (fixes #105) --- .../footageproperties/footageproperties.cpp | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/app/dialog/footageproperties/footageproperties.cpp b/app/dialog/footageproperties/footageproperties.cpp index 2134b3666..3fe507906 100644 --- a/app/dialog/footageproperties/footageproperties.cpp +++ b/app/dialog/footageproperties/footageproperties.cpp @@ -66,7 +66,11 @@ FootagePropertiesDialog::FootagePropertiesDialog(QWidget *parent, Footage *foota stacked_widget_ = new QStackedWidget(); layout->addWidget(stacked_widget_, row, 0, 1, 2); - foreach (StreamPtr stream, footage_->streams()) { + int first_usable_stream = -1; + + for (int i=0;istreams().size();i++) { + StreamPtr stream = footage_->stream(i); + QListWidgetItem* item = new QListWidgetItem(stream->description(), track_list); item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setCheckState(stream->enabled() ? Qt::Checked : Qt::Unchecked); @@ -83,18 +87,31 @@ FootagePropertiesDialog::FootagePropertiesDialog(QWidget *parent, Footage *foota default: stacked_widget_->addWidget(new StreamProperties()); } + + if (first_usable_stream == -1 + && (stream->type() == Stream::kVideo + || stream->type() == Stream::kAudio + || stream->type() == Stream::kImage)) { + first_usable_stream = i; + } } row++; - connect(track_list, SIGNAL(currentRowChanged(int)), stacked_widget_, SLOT(setCurrentIndex(int))); - QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); buttons->setCenterButtons(true); layout->addWidget(buttons, row, 0, 1, 2); connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); + + connect(track_list, &QListWidget::currentRowChanged, stacked_widget_, &QStackedWidget::setCurrentIndex); + + // Auto-select first item that actually has properties + if (first_usable_stream >= 0) { + track_list->item(first_usable_stream)->setSelected(true); + } + track_list->setFocus(); } void FootagePropertiesDialog::accept() { From 63f3e542eee1cf3558865b1f4152c9fa0f68c532 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 4 May 2020 12:58:28 +1000 Subject: [PATCH 15/15] style: fixed windows compile issue --- app/window/mainwindow/mainmenu.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/window/mainwindow/mainmenu.cpp b/app/window/mainwindow/mainmenu.cpp index 46e7527f5..588939887 100644 --- a/app/window/mainwindow/mainmenu.cpp +++ b/app/window/mainwindow/mainmenu.cpp @@ -39,9 +39,7 @@ OLIVE_NAMESPACE_ENTER MainMenu::MainMenu(MainWindow *parent) : QMenuBar(parent) { -#ifdef Q_OS_WINDOWS - StyleManager::UseNativeWindowsStyling(this); -#endif + StyleManager::UseOSNativeStyling(this); // // FILE MENU