Simplified interface, optim to read directly from the resampler if enough samples are available

This commit is contained in:
emeric
2026-02-17 23:15:44 +01:00
parent e0f1e4a1ec
commit 6742c99ed8
5 changed files with 80 additions and 30 deletions
+1 -1
View File
@@ -37,7 +37,7 @@ __Notes__:
* a C++20 compiler is needed * a C++20 compiler is needed
* ffmpeg version 4 minimum is required * ffmpeg version 4 minimum is required
```sh ```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__: __Notes__:
* libpam0g-dev is optional (only for using PAM authentication) * libpam0g-dev is optional (only for using PAM authentication)
+70 -26
View File
@@ -19,6 +19,7 @@
#include "PcmDecoder.hpp" #include "PcmDecoder.hpp"
#include <algorithm>
#include <array> #include <array>
extern "C" extern "C"
@@ -166,27 +167,15 @@ namespace lms::audio::ffmpeg
PcmDecoder::~PcmDecoder() = default; PcmDecoder::~PcmDecoder() = default;
std::size_t PcmDecoder::readSamples(std::span<WritableBuffer> outputChannelBuffers, std::size_t maxSamplesPerChannel) std::size_t PcmDecoder::readSamples(std::span<WritableBuffer> outputChannelBuffers)
{ {
assert(outputChannelBuffers.size() <= AV_NUM_DATA_POINTERS);
if (_finished) if (_finished)
return 0; return 0;
if (_parameters.planar) const std::size_t maxSamplesPerChannel{ computeSampleCountPerChannel(outputChannelBuffers) };
{
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" };
}
std::array<uint8_t*, AV_NUM_DATA_POINTERS> outData{}; if (getEstimatedResamplerAvailableSamples() >= maxSamplesPerChannel)
for (size_t i = 0; i < outputChannelBuffers.size(); ++i) return drainResampler(outputChannelBuffers, maxSamplesPerChannel);
outData[i] = reinterpret_cast<uint8_t*>(outputChannelBuffers[i].data());
while (true) while (true)
{ {
@@ -212,6 +201,10 @@ namespace lms::audio::ffmpeg
} }
else else
{ {
std::array<uint8_t*, AV_NUM_DATA_POINTERS> outData{};
for (size_t i = 0; i < outputChannelBuffers.size(); ++i)
outData[i] = reinterpret_cast<uint8_t*>(outputChannelBuffers[i].data());
// Resample decoded audio // Resample decoded audio
const int outSampleCount{ ::swr_convert( const int outSampleCount{ ::swr_convert(
_resampleContext.get(), _resampleContext.get(),
@@ -233,20 +226,12 @@ namespace lms::audio::ffmpeg
// Drain resampler once decoder is drained // Drain resampler once decoder is drained
if (_draining) if (_draining)
{ {
const int outSampleCount = ::swr_convert(_resampleContext.get(), const std::size_t outSampleCount{ drainResampler(outputChannelBuffers, maxSamplesPerChannel) };
outData.data(),
static_cast<int>(maxSamplesPerChannel),
nullptr,
0);
if (outSampleCount < 0)
throw FFmpegException{ "swr_convert (drain) failed", outSampleCount };
if (outSampleCount > 0) if (outSampleCount > 0)
return outSampleCount; return outSampleCount;
_finished = true; _finished = true;
return 0; break;
} }
} }
@@ -258,6 +243,39 @@ namespace lms::audio::ffmpeg
return _finished; return _finished;
} }
std::size_t PcmDecoder::computeSampleCountPerChannel(std::span<WritableBuffer> 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() void PcmDecoder::feedDecoder()
{ {
assert(!_eof); assert(!_eof);
@@ -286,4 +304,30 @@ namespace lms::audio::ffmpeg
::av_packet_unref(_inputPacket.get()); ::av_packet_unref(_inputPacket.get());
} }
} }
std::size_t PcmDecoder::drainResampler(std::span<WritableBuffer> outputChannelBuffers, std::size_t maxSamplesPerChannel)
{
std::array<uint8_t*, AV_NUM_DATA_POINTERS> outData{};
for (size_t i = 0; i < outputChannelBuffers.size(); ++i)
outData[i] = reinterpret_cast<uint8_t*>(outputChannelBuffers[i].data());
const int outSampleCount{ ::swr_convert(_resampleContext.get(),
outData.data(),
static_cast<int>(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 } // namespace lms::audio::ffmpeg
+4 -1
View File
@@ -35,10 +35,13 @@ namespace lms::audio::ffmpeg
PcmDecoder& operator=(const PcmDecoder&) = delete; PcmDecoder& operator=(const PcmDecoder&) = delete;
private: private:
std::size_t readSamples(std::span<WritableBuffer> outputChannelBuffers, std::size_t maxSamplesPerChannel) override; std::size_t readSamples(std::span<WritableBuffer> outputChannelBuffers) override;
bool finished() const override; bool finished() const override;
std::size_t computeSampleCountPerChannel(std::span<WritableBuffer> outputChannelBuffers) const;
void feedDecoder(); void feedDecoder();
std::size_t drainResampler(std::span<WritableBuffer> outputChannelBuffers, std::size_t maxSamplesPerChannel);
std::size_t getEstimatedResamplerAvailableSamples() const;
const PcmDecoderParameters _parameters; const PcmDecoderParameters _parameters;
+4 -1
View File
@@ -53,7 +53,10 @@ namespace lms::audio
// Returns the number of samples written per channel. Returns 0 only once all remaining samples are drained. // 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 // Provide one buffer per channel if planar, or a single buffer containing all channels interleaved
virtual std::size_t readSamples(std::span<WritableBuffer> 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<WritableBuffer> outputChannelBuffers) = 0;
virtual bool finished() const = 0; virtual bool finished() const = 0;
}; };
+1 -1
View File
@@ -89,7 +89,7 @@ int main(int argc, char* argv[])
std::span<std::byte>{ channelBuffers[0].data(), channelBuffers[0].size() }, std::span<std::byte>{ channelBuffers[0].data(), channelBuffers[0].size() },
std::span<std::byte>{ channelBuffers[1].data(), channelBuffers[1].size() } std::span<std::byte>{ 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; totalSampleCount += sampleCount;
} }