diff --git a/app/decoder/decoder.cpp b/app/decoder/decoder.cpp index 02ddd83c4..d0e1732af 100644 --- a/app/decoder/decoder.cpp +++ b/app/decoder/decoder.cpp @@ -134,3 +134,10 @@ DecoderPtr Decoder::CreateFromID(const QString &id) return nullptr; } + +void Decoder::Conform(const AudioRenderingParams ¶ms) +{ + Q_UNUSED(params) + qCritical() << "Conform called on an audio decoder that does not have a handler for it:" << id(); + abort(); +} diff --git a/app/decoder/decoder.h b/app/decoder/decoder.h index 3b810585e..6e7db488a 100644 --- a/app/decoder/decoder.h +++ b/app/decoder/decoder.h @@ -180,6 +180,19 @@ public: */ static DecoderPtr CreateFromID(const QString& id); + /** + * @brief Conform an audio stream to match certain parameters (audio only) + * + * 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. + */ + virtual void Conform(const AudioRenderingParams& params); + protected: bool open_; diff --git a/app/decoder/ffmpeg/ffmpegdecoder.cpp b/app/decoder/ffmpeg/ffmpegdecoder.cpp index 85bc93a7d..85b15d8a2 100644 --- a/app/decoder/ffmpeg/ffmpegdecoder.cpp +++ b/app/decoder/ffmpeg/ffmpegdecoder.cpp @@ -328,6 +328,68 @@ int64_t FFmpegDecoder::GetTimestampFromTime(const rational &time) return target_ts; } +void FFmpegDecoder::Conform(const AudioRenderingParams ¶ms) +{ + if (avstream_->codecpar->codec_type != AVMEDIA_TYPE_AUDIO) { + // Nothing to be done + return; + } + + if (!LoadIndex()) { + Index(); + } + + // Get indexed WAV file + WaveInput input(GetIndexFilename()); + + if (input.open()) { + // If the parameters are equal, nothing to be done + 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()), + GetFFmpegSampleFormat(params.format()), + params.sample_rate(), + static_cast(input.params().channel_layout()), + GetFFmpegSampleFormat(input.params().format()), + input.params().sample_rate(), + 0, + nullptr); + + WaveOutput conformed_output(conformed_fn, params); + if (!conformed_output.open()) { + qWarning() << "Failed to open conformed output:" << conformed_fn; + input.close(); + return; + } + + // + //swr_convert(resampler, input.) + + // Clean up + swr_free(&resampler); + conformed_output.close(); + input.close(); + } else { + qWarning() << "Failed to conform file:" << stream()->footage()->filename(); + } +} + bool FFmpegDecoder::Probe(Footage *f) { if (open_) { @@ -530,6 +592,20 @@ QString FFmpegDecoder::GetIndexFilename() .append(QString::number(avstream_->index)); } +QString FFmpegDecoder::GetConformedFilename(const AudioRenderingParams ¶ms) +{ + QString index_fn = GetIndexFilename(); + + 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; +} + bool FFmpegDecoder::LoadIndex() { switch (avstream_->codecpar->codec_type) { @@ -620,7 +696,7 @@ void FFmpegDecoder::IndexAudio(AVPacket *pkt, AVFrame *frame) WaveOutput wave_out(GetIndexFilename(), AudioRenderingParams(avstream_->codecpar->sample_rate, channel_layout, - GetNativeSampleRate(dst_sample_fmt))); + GetNativeSampleFormat(dst_sample_fmt))); int ret; @@ -782,7 +858,7 @@ AVPixelFormat FFmpegDecoder::GetCompatiblePixelFormat(const AVPixelFormat &pix_f nullptr); } -SampleFormat FFmpegDecoder::GetNativeSampleRate(const AVSampleFormat &smp_fmt) +SampleFormat FFmpegDecoder::GetNativeSampleFormat(const AVSampleFormat &smp_fmt) { switch (smp_fmt) { case AV_SAMPLE_FMT_U8: @@ -811,6 +887,29 @@ SampleFormat FFmpegDecoder::GetNativeSampleRate(const AVSampleFormat &smp_fmt) return SAMPLE_FMT_INVALID; } +AVSampleFormat FFmpegDecoder::GetFFmpegSampleFormat(const SampleFormat &smp_fmt) +{ + switch (smp_fmt) { + case SAMPLE_FMT_U8: + return AV_SAMPLE_FMT_U8; + case SAMPLE_FMT_S16: + return AV_SAMPLE_FMT_S16; + case SAMPLE_FMT_S32: + return AV_SAMPLE_FMT_S32; + case SAMPLE_FMT_S64: + return AV_SAMPLE_FMT_S64; + case SAMPLE_FMT_FLT: + return AV_SAMPLE_FMT_FLT; + case SAMPLE_FMT_DBL: + return AV_SAMPLE_FMT_DBL; + case SAMPLE_FMT_INVALID: + case SAMPLE_FMT_COUNT: + break; + } + + return AV_SAMPLE_FMT_NONE; +} + int FFmpegDecoder::CalculatePlaneHeight(int frame_height, const AVPixelFormat &format, int plane) { // FIXME: This seems dumb, but I can't find any FFmpeg function that returns this information diff --git a/app/decoder/ffmpeg/ffmpegdecoder.h b/app/decoder/ffmpeg/ffmpegdecoder.h index 3e2860f97..5271f40ed 100644 --- a/app/decoder/ffmpeg/ffmpegdecoder.h +++ b/app/decoder/ffmpeg/ffmpegdecoder.h @@ -54,6 +54,8 @@ public: virtual int64_t GetTimestampFromTime(const rational& time) override; + virtual void Conform(const AudioRenderingParams& params) override; + private: /** * @brief Handle an error @@ -100,11 +102,14 @@ private: * @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. - * - * @return */ QString GetIndexFilename(); + /** + * @brief Get the destination filename of an audio stream conformed to a set of parameters + */ + QString GetConformedFilename(const AudioRenderingParams ¶ms); + /** * @brief Used internally to load a frame index into frame_index_ * @@ -132,7 +137,9 @@ private: */ AVPixelFormat GetCompatiblePixelFormat(const AVPixelFormat& pix_fmt); - SampleFormat GetNativeSampleRate(const AVSampleFormat& smp_fmt); + SampleFormat GetNativeSampleFormat(const AVSampleFormat& smp_fmt); + + AVSampleFormat GetFFmpegSampleFormat(const SampleFormat& smp_fmt); int CalculatePlaneHeight(int frame_height, const AVPixelFormat& format, int plane); diff --git a/app/decoder/waveinput.cpp b/app/decoder/waveinput.cpp index 9477139c7..3aa292120 100644 --- a/app/decoder/waveinput.cpp +++ b/app/decoder/waveinput.cpp @@ -124,7 +124,8 @@ bool WaveInput::open() return false; } - data_position_ = file_.pos() + 4; + data_stream >> data_size_; + data_position_ = file_.pos(); return true; } @@ -166,6 +167,11 @@ void WaveInput::close() } } +int WaveInput::sample_count() +{ + return params_.bytes_to_samples(static_cast(data_size_)); +} + bool WaveInput::find_str(QFile *f, const char *str) { qint64 pos = f->pos(); diff --git a/app/decoder/waveinput.h b/app/decoder/waveinput.h index d23e43e80..b71bf9fbf 100644 --- a/app/decoder/waveinput.h +++ b/app/decoder/waveinput.h @@ -26,6 +26,8 @@ public: void close(); + int sample_count(); + private: bool find_str(QFile* f, const char* str); @@ -34,6 +36,8 @@ private: QFile file_; qint64 data_position_; + + quint32 data_size_; }; #endif // WAVEINPUT_H diff --git a/app/render/audioparams.cpp b/app/render/audioparams.cpp index d8d10103c..43dafa08c 100644 --- a/app/render/audioparams.cpp +++ b/app/render/audioparams.cpp @@ -83,6 +83,13 @@ int AudioRenderingParams::samples_to_bytes(const int &samples) const return samples * channel_count() * bytes_per_sample_per_channel(); } +int AudioRenderingParams::bytes_to_samples(const int &bytes) const +{ + Q_ASSERT(is_valid()); + + return bytes / (channel_count() * bytes_per_sample_per_channel()); +} + int AudioRenderingParams::channel_count() const { return av_get_channel_layout_nb_channels(channel_layout()); diff --git a/app/render/audioparams.h b/app/render/audioparams.h index 7c6a3c5ee..5b725c3ed 100644 --- a/app/render/audioparams.h +++ b/app/render/audioparams.h @@ -31,6 +31,7 @@ public: int time_to_bytes(const rational& time) const; int time_to_samples(const rational& time) const; int samples_to_bytes(const int& samples) const; + int bytes_to_samples(const int &bytes) const; int channel_count() const; int bytes_per_sample_per_channel() const; int bits_per_sample() const;