From 08e6c6ffc0751b7ae0940c1c15a2ad592d7be5ba Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 19 Feb 2020 12:27:41 +1100 Subject: [PATCH] decoder: move conform function to base class There's no reason the Conform() function can't live in the base class since its functionality isn't necessarily specific to FFmpeg. --- app/codec/decoder.cpp | 143 ++++++++++++++++++++++++++++- app/codec/decoder.h | 20 +++- app/codec/ffmpeg/ffmpegdecoder.cpp | 139 ---------------------------- app/codec/ffmpeg/ffmpegdecoder.h | 16 +--- app/codec/oiio/oiiodecoder.cpp | 5 + app/codec/oiio/oiiodecoder.h | 2 + 6 files changed, 166 insertions(+), 159 deletions(-) diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index 88a824a1f..3e117e847 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -24,8 +24,11 @@ #include #include +#include "codec/ffmpeg/ffmpegcommon.h" #include "codec/ffmpeg/ffmpegdecoder.h" #include "codec/oiio/oiiodecoder.h" +#include "codec/waveinput.h" +#include "codec/waveoutput.h" #include "render/indexmanager.h" #include "task/index/index.h" #include "task/taskmanager.h" @@ -172,12 +175,144 @@ DecoderPtr Decoder::CreateFromID(const QString &id) void Decoder::Conform(const AudioRenderingParams ¶ms, const QAtomicInt* cancelled) { - Q_UNUSED(params) - qCritical() << "Conform called on an audio decoder that does not have a handler for it:" << id(); - abort(); + if (stream()->type() != Stream::kAudio) { + // Nothing to be done + return; + } + + Index(cancelled); + + // Get indexed WAV file + 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 + // FIXME: Technically we only need to conform if the SAMPLE RATE is not equal. Format and channel layout conversion + // could be done on the fly so we could perhaps conform less often at some point. + if (input.params() == params) { + input.close(); + return; + } + + // Otherwise, let's start converting the format + + // Generate destination filename for this conversion to see if it exists + QString conformed_fn = GetConformedFilename(params); + + if (QFileInfo::exists(conformed_fn)) { + // We must have already conformed this format + input.close(); + return; + } + + // Set up resampler + SwrContext* resampler = swr_alloc_set_opts(nullptr, + static_cast(params.channel_layout()), + FFmpegCommon::GetFFmpegSampleFormat(params.format()), + params.sample_rate(), + static_cast(input.params().channel_layout()), + FFmpegCommon::GetFFmpegSampleFormat(input.params().format()), + input.params().sample_rate(), + 0, + nullptr); + + swr_init(resampler); + + WaveOutput conformed_output(conformed_fn, params); + if (!conformed_output.open()) { + qWarning() << "Failed to open conformed output:" << conformed_fn; + input.close(); + return; + } + + // Convert one second of audio at a time + int input_buffer_sz = input.params().time_to_bytes(1); + + while (!input.at_end()) { + if (cancelled && *cancelled) { + break; + } + + // Read up to one second of audio from WAV file + QByteArray read_samples = input.read(input_buffer_sz); + + // Determine how many samples this is + int in_sample_count = input.params().bytes_to_samples(read_samples.size()); + + ConformInternal(resampler, &conformed_output, read_samples.data(), in_sample_count); + } + + // Flush resampler + ConformInternal(resampler, &conformed_output, nullptr, 0); + + // Clean up + swr_free(&resampler); + conformed_output.close(); + input.close(); + + // If we cancelled, the conform didn't finish so remove it + if (cancelled && *cancelled) { + QFile(conformed_fn).remove(); + } + } else { + qWarning() << "Failed to conform file:" << stream()->footage()->filename(); + } } -void Decoder::Index(const QAtomicInt *cancelled) +void Decoder::ConformInternal(SwrContext* resampler, WaveOutput* output, const char* in_data, int in_sample_count) +{ + // Determine how many samples the output will be + int out_sample_count = swr_get_out_samples(resampler, in_sample_count); + + // Allocate array for the amount of samples we'll need + QByteArray out_samples; + out_samples.resize(output->params().samples_to_bytes(out_sample_count)); + + char* out_data = out_samples.data(); + + // Convert samples + int convert_count = swr_convert(resampler, + reinterpret_cast(&out_data), + out_sample_count, + reinterpret_cast(&in_data), + in_sample_count); + + if (convert_count != out_sample_count) { + out_samples.resize(output->params().samples_to_bytes(convert_count)); + } + + output->write(out_samples); +} + +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())); + index_fn.append('.'); + index_fn.append(QString::number(params.format())); + index_fn.append('.'); + index_fn.append(QString::number(params.channel_layout())); + + return index_fn; +} + +void Decoder::Index(const QAtomicInt *) { } diff --git a/app/codec/decoder.h b/app/codec/decoder.h index 3524ce599..9a6d45ce8 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -25,6 +25,7 @@ #include #include "codec/frame.h" +#include "codec/waveoutput.h" #include "common/constructors.h" #include "common/rational.h" #include "project/item/footage/footage.h" @@ -32,6 +33,8 @@ class Decoder; using DecoderPtr = std::shared_ptr; +struct SwrContext; + /** * @brief A decoder's is the main class for bringing external media into Olive * @@ -221,7 +224,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. */ - virtual void Conform(const AudioRenderingParams& params, const QAtomicInt* cancelled); + void Conform(const AudioRenderingParams& params, const QAtomicInt* cancelled); /** * @brief Create an index for this media @@ -243,10 +246,25 @@ signals: protected: void SignalIndexProgress(const int64_t& ts); + /** + * @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. + */ + virtual QString GetIndexFilename() = 0; + + /** + * @brief Get the destination filename of an audio stream conformed to a set of parameters + */ + QString GetConformedFilename(const AudioRenderingParams ¶ms); + bool open_; private: + void ConformInternal(SwrContext *resampler, WaveOutput *output, const char *in_data, int in_sample_count); + StreamPtr stream_; + }; Q_DECLARE_METATYPE(Decoder::RetrieveState) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 2f890b446..4e41af707 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -416,93 +416,6 @@ QString FFmpegDecoder::id() return "ffmpeg"; } -void FFmpegDecoder::Conform(const AudioRenderingParams ¶ms, const QAtomicInt* cancelled) -{ - if (avstream_->codecpar->codec_type != AVMEDIA_TYPE_AUDIO) { - // Nothing to be done - return; - } - - Index(cancelled); - - // Get indexed WAV file - 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 - // FIXME: Technically we only need to conform if the SAMPLE RATE is not equal. Format and channel layout conversion - // could be done on the fly so we could perhaps conform less often at some point. - if (input.params() == params) { - input.close(); - return; - } - - // Otherwise, let's start converting the format - - // Generate destination filename for this conversion to see if it exists - QString conformed_fn = GetConformedFilename(params); - - if (QFileInfo::exists(conformed_fn)) { - // We must have already conformed this format - input.close(); - return; - } - - // Set up resampler - SwrContext* resampler = swr_alloc_set_opts(nullptr, - static_cast(params.channel_layout()), - FFmpegCommon::GetFFmpegSampleFormat(params.format()), - params.sample_rate(), - static_cast(input.params().channel_layout()), - FFmpegCommon::GetFFmpegSampleFormat(input.params().format()), - input.params().sample_rate(), - 0, - nullptr); - - swr_init(resampler); - - WaveOutput conformed_output(conformed_fn, params); - if (!conformed_output.open()) { - qWarning() << "Failed to open conformed output:" << conformed_fn; - input.close(); - return; - } - - // Convert one second of audio at a time - int input_buffer_sz = input.params().time_to_bytes(1); - - while (!input.at_end()) { - if (cancelled && *cancelled) { - break; - } - - // Read up to one second of audio from WAV file - QByteArray read_samples = input.read(input_buffer_sz); - - // Determine how many samples this is - int in_sample_count = input.params().bytes_to_samples(read_samples.size()); - - ConformInternal(resampler, &conformed_output, read_samples.data(), in_sample_count); - } - - // Flush resampler - ConformInternal(resampler, &conformed_output, nullptr, 0); - - // Clean up - swr_free(&resampler); - conformed_output.close(); - input.close(); - - // If we cancelled, the conform didn't finish so remove it - if (cancelled && *cancelled) { - QFile(conformed_fn).remove(); - } - } else { - qWarning() << "Failed to conform file:" << stream()->footage()->filename(); - } -} - bool FFmpegDecoder::SupportsVideo() { return true; @@ -518,31 +431,6 @@ void FFmpegDecoder::SetMultithreading(bool e) multithreading_ = e; } -void FFmpegDecoder::ConformInternal(SwrContext* resampler, WaveOutput* output, const char* in_data, int in_sample_count) -{ - // Determine how many samples the output will be - int out_sample_count = swr_get_out_samples(resampler, in_sample_count); - - // Allocate array for the amount of samples we'll need - QByteArray out_samples; - out_samples.resize(output->params().samples_to_bytes(out_sample_count)); - - char* out_data = out_samples.data(); - - // Convert samples - int convert_count = swr_convert(resampler, - reinterpret_cast(&out_data), - out_sample_count, - reinterpret_cast(&in_data), - in_sample_count); - - if (convert_count != out_sample_count) { - out_samples.resize(output->params().samples_to_bytes(convert_count)); - } - - output->write(out_samples); -} - bool FFmpegDecoder::Probe(Footage *f, const QAtomicInt* cancelled) { if (open_) { @@ -742,33 +630,6 @@ QString FFmpegDecoder::GetIndexFilename() .append(QString::number(avstream_->index)); } -QString FFmpegDecoder::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())); - index_fn.append('.'); - index_fn.append(QString::number(params.format())); - index_fn.append('.'); - index_fn.append(QString::number(params.channel_layout())); - - return index_fn; -} - void FFmpegDecoder::UnconditionalAudioIndex(AVPacket *pkt, AVFrame *frame, const QAtomicInt* cancelled) { // Iterate through each audio frame and extract the PCM data diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index 0cee6e12e..a802da64a 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -57,8 +57,6 @@ public: virtual QString id() override; - virtual void Conform(const AudioRenderingParams& params, const QAtomicInt *cancelled) override; - virtual bool SupportsVideo() override; virtual bool SupportsAudio() override; @@ -67,8 +65,6 @@ public: virtual void Index(const QAtomicInt *cancelled) override; private: - void ConformInternal(SwrContext *resampler, WaveOutput *output, const char *in_data, int in_sample_count); - /** * @brief Handle an error * @@ -97,17 +93,7 @@ private: */ int GetFrame(AVPacket* pkt, AVFrame* frame); - /** - * @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. - */ - QString GetIndexFilename(); - - /** - * @brief Get the destination filename of an audio stream conformed to a set of parameters - */ - QString GetConformedFilename(const AudioRenderingParams ¶ms); + virtual QString GetIndexFilename() override; void UnconditionalAudioIndex(AVPacket* pkt, AVFrame* frame, const QAtomicInt* cancelled); void UnconditionalVideoIndex(AVPacket* pkt, AVFrame* frame, const QAtomicInt* cancelled); diff --git a/app/codec/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp index c8ab99bf0..898d6ea8d 100644 --- a/app/codec/oiio/oiiodecoder.cpp +++ b/app/codec/oiio/oiiodecoder.cpp @@ -187,3 +187,8 @@ bool OIIODecoder::SupportsVideo() { return true; } + +QString OIIODecoder::GetIndexFilename() +{ + return QString(); +} diff --git a/app/codec/oiio/oiiodecoder.h b/app/codec/oiio/oiiodecoder.h index 5904da6d3..8f156cb75 100644 --- a/app/codec/oiio/oiiodecoder.h +++ b/app/codec/oiio/oiiodecoder.h @@ -44,6 +44,8 @@ public: virtual bool SupportsVideo() override; + virtual QString GetIndexFilename() override; + private: #if OIIO_VERSION < 10903 OIIO::ImageInput* image_;