From 6742c99ed84b6cc68a214709883de0c84685adcd Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 18 Jan 2026 11:55:50 +0100 Subject: [PATCH] Simplified interface, optim to read directly from the resampler if enough samples are available --- INSTALL.md | 2 +- src/libs/audio/impl/ffmpeg/PcmDecoder.cpp | 96 ++++++++++++++------ src/libs/audio/impl/ffmpeg/PcmDecoder.hpp | 5 +- src/libs/audio/include/audio/IPcmDecoder.hpp | 5 +- src/tools/audiodecode/LmsAudioDecode.cpp | 2 +- 5 files changed, 80 insertions(+), 30 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 55a5455b..d888d7c5 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -37,7 +37,7 @@ __Notes__: * a C++20 compiler is needed * ffmpeg version 4 minimum is required ```sh -apt-get install build-essential cmake libboost-program-options-dev libboost-system-dev libavutil-dev libavformat-dev libstb-dev libconfig++-dev ffmpeg libtag-dev libpam0g-dev libpugixml-dev libgtest-dev libarchive-dev libxxhash-dev libssl-dev +apt-get install build-essential cmake libboost-program-options-dev libboost-system-dev libavutil-dev libavformat-dev libswresample-dev ffmpeg libconfig++-dev libstb-dev libtag-dev libpam0g-dev libpugixml-dev libgtest-dev libarchive-dev libxxhash-dev libssl-dev ``` __Notes__: * libpam0g-dev is optional (only for using PAM authentication) diff --git a/src/libs/audio/impl/ffmpeg/PcmDecoder.cpp b/src/libs/audio/impl/ffmpeg/PcmDecoder.cpp index b418f428..57777847 100644 --- a/src/libs/audio/impl/ffmpeg/PcmDecoder.cpp +++ b/src/libs/audio/impl/ffmpeg/PcmDecoder.cpp @@ -19,6 +19,7 @@ #include "PcmDecoder.hpp" +#include #include extern "C" @@ -166,27 +167,15 @@ namespace lms::audio::ffmpeg PcmDecoder::~PcmDecoder() = default; - std::size_t PcmDecoder::readSamples(std::span outputChannelBuffers, std::size_t maxSamplesPerChannel) + std::size_t PcmDecoder::readSamples(std::span outputChannelBuffers) { - assert(outputChannelBuffers.size() <= AV_NUM_DATA_POINTERS); - if (_finished) return 0; - if (_parameters.planar) - { - if (outputChannelBuffers.size() != _parameters.channelCount) - throw Exception{ "Expected " + std::to_string(_parameters.channelCount) + " buffers for planar output" }; - } - else - { - if (outputChannelBuffers.size() != 1) - throw Exception{ "Expected a single buffer for interleaved output" }; - } + const std::size_t maxSamplesPerChannel{ computeSampleCountPerChannel(outputChannelBuffers) }; - std::array outData{}; - for (size_t i = 0; i < outputChannelBuffers.size(); ++i) - outData[i] = reinterpret_cast(outputChannelBuffers[i].data()); + if (getEstimatedResamplerAvailableSamples() >= maxSamplesPerChannel) + return drainResampler(outputChannelBuffers, maxSamplesPerChannel); while (true) { @@ -212,6 +201,10 @@ namespace lms::audio::ffmpeg } else { + std::array outData{}; + for (size_t i = 0; i < outputChannelBuffers.size(); ++i) + outData[i] = reinterpret_cast(outputChannelBuffers[i].data()); + // Resample decoded audio const int outSampleCount{ ::swr_convert( _resampleContext.get(), @@ -233,20 +226,12 @@ namespace lms::audio::ffmpeg // Drain resampler once decoder is drained if (_draining) { - const int outSampleCount = ::swr_convert(_resampleContext.get(), - outData.data(), - static_cast(maxSamplesPerChannel), - nullptr, - 0); - - if (outSampleCount < 0) - throw FFmpegException{ "swr_convert (drain) failed", outSampleCount }; - + const std::size_t outSampleCount{ drainResampler(outputChannelBuffers, maxSamplesPerChannel) }; if (outSampleCount > 0) return outSampleCount; _finished = true; - return 0; + break; } } @@ -258,6 +243,39 @@ namespace lms::audio::ffmpeg return _finished; } + std::size_t PcmDecoder::computeSampleCountPerChannel(std::span outputChannelBuffers) const + { + if (_parameters.planar) + { + if (outputChannelBuffers.size() != _parameters.channelCount) + throw Exception{ "Expected " + std::to_string(_parameters.channelCount) + " buffers for planar output" }; + + // Each planar buffer holds samples for one channel only + const int bytesPerSample{ av_get_bytes_per_sample(toAvSampleFormat(_parameters.sampleType, true)) }; + if (bytesPerSample <= 0) + throw Exception{ "Invalid bytes per sample for output format" }; + + const std::size_t sampleCount{ outputChannelBuffers[0].size() / bytesPerSample }; + if (!std::all_of(std::cbegin(outputChannelBuffers), std::cend(outputChannelBuffers), [&](const WritableBuffer& buffer) { return buffer.size() == outputChannelBuffers[0].size(); })) + throw Exception{ "All planar channel buffers must have the same size" }; + + return sampleCount; + } + + // interleaved + if (outputChannelBuffers.size() != 1) + throw Exception{ "Expected a single buffer for interleaved output" }; + + const int bytesPerSample = av_get_bytes_per_sample(toAvSampleFormat(_parameters.sampleType, false)); + if (bytesPerSample <= 0) + throw Exception{ "Invalid bytes per sample for output format" }; + + // Divide by (bytes per sample * number of channels) for interleaved + const std::size_t sampleCount = outputChannelBuffers[0].size() / (bytesPerSample * _parameters.channelCount); + + return sampleCount; + } + void PcmDecoder::feedDecoder() { assert(!_eof); @@ -286,4 +304,30 @@ namespace lms::audio::ffmpeg ::av_packet_unref(_inputPacket.get()); } } + + std::size_t PcmDecoder::drainResampler(std::span outputChannelBuffers, std::size_t maxSamplesPerChannel) + { + std::array outData{}; + for (size_t i = 0; i < outputChannelBuffers.size(); ++i) + outData[i] = reinterpret_cast(outputChannelBuffers[i].data()); + + const int outSampleCount{ ::swr_convert(_resampleContext.get(), + outData.data(), + static_cast(maxSamplesPerChannel), + nullptr, + 0) }; + + if (outSampleCount < 0) + throw FFmpegException{ "swr_convert (drain) failed", outSampleCount }; + + return outSampleCount; + } + + std::size_t PcmDecoder::getEstimatedResamplerAvailableSamples() const + { + const int64_t delayedInputSampleCount{ ::swr_get_delay(_resampleContext.get(), _decoderContext->sample_rate) }; + const int64_t sampleCount{ av_rescale_rnd(delayedInputSampleCount, _parameters.sampleRate, _decoderContext->sample_rate, AV_ROUND_UP) }; + + return sampleCount; + } } // namespace lms::audio::ffmpeg \ No newline at end of file diff --git a/src/libs/audio/impl/ffmpeg/PcmDecoder.hpp b/src/libs/audio/impl/ffmpeg/PcmDecoder.hpp index a136e007..6d026c7d 100644 --- a/src/libs/audio/impl/ffmpeg/PcmDecoder.hpp +++ b/src/libs/audio/impl/ffmpeg/PcmDecoder.hpp @@ -35,10 +35,13 @@ namespace lms::audio::ffmpeg PcmDecoder& operator=(const PcmDecoder&) = delete; private: - std::size_t readSamples(std::span outputChannelBuffers, std::size_t maxSamplesPerChannel) override; + std::size_t readSamples(std::span outputChannelBuffers) override; bool finished() const override; + std::size_t computeSampleCountPerChannel(std::span outputChannelBuffers) const; void feedDecoder(); + std::size_t drainResampler(std::span outputChannelBuffers, std::size_t maxSamplesPerChannel); + std::size_t getEstimatedResamplerAvailableSamples() const; const PcmDecoderParameters _parameters; diff --git a/src/libs/audio/include/audio/IPcmDecoder.hpp b/src/libs/audio/include/audio/IPcmDecoder.hpp index b84cc848..dfb2e121 100644 --- a/src/libs/audio/include/audio/IPcmDecoder.hpp +++ b/src/libs/audio/include/audio/IPcmDecoder.hpp @@ -53,7 +53,10 @@ namespace lms::audio // Returns the number of samples written per channel. Returns 0 only once all remaining samples are drained. // Provide one buffer per channel if planar, or a single buffer containing all channels interleaved - virtual std::size_t readSamples(std::span outputChannelBuffers, std::size_t maxSamplesPerChannel) = 0; + // Each buffer must be sized to hold an integer number of samples according to the requested sample type. + // For example, for Float32 planar output, each buffer size must be divisible by sizeof(float). + // The decoder will use the buffer sizes to determine the maximum number of samples it can write. + virtual std::size_t readSamples(std::span outputChannelBuffers) = 0; virtual bool finished() const = 0; }; diff --git a/src/tools/audiodecode/LmsAudioDecode.cpp b/src/tools/audiodecode/LmsAudioDecode.cpp index cf8c8ced..aaf4022a 100644 --- a/src/tools/audiodecode/LmsAudioDecode.cpp +++ b/src/tools/audiodecode/LmsAudioDecode.cpp @@ -89,7 +89,7 @@ int main(int argc, char* argv[]) std::span{ channelBuffers[0].data(), channelBuffers[0].size() }, std::span{ channelBuffers[1].data(), channelBuffers[1].size() } }; - const std::size_t sampleCount{ decoder->readSamples(outputBuffers, sampleCountPerChannel) }; + const std::size_t sampleCount{ decoder->readSamples(outputBuffers) }; totalSampleCount += sampleCount; }