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
+70 -26
View File
@@ -19,6 +19,7 @@
#include "PcmDecoder.hpp"
#include <algorithm>
#include <array>
extern "C"
@@ -166,27 +167,15 @@ namespace lms::audio::ffmpeg
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)
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<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());
if (getEstimatedResamplerAvailableSamples() >= maxSamplesPerChannel)
return drainResampler(outputChannelBuffers, maxSamplesPerChannel);
while (true)
{
@@ -212,6 +201,10 @@ namespace lms::audio::ffmpeg
}
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
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<int>(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<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()
{
assert(!_eof);
@@ -286,4 +304,30 @@ namespace lms::audio::ffmpeg
::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
+4 -1
View File
@@ -35,10 +35,13 @@ namespace lms::audio::ffmpeg
PcmDecoder& operator=(const PcmDecoder&) = delete;
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;
std::size_t computeSampleCountPerChannel(std::span<WritableBuffer> outputChannelBuffers) const;
void feedDecoder();
std::size_t drainResampler(std::span<WritableBuffer> outputChannelBuffers, std::size_t maxSamplesPerChannel);
std::size_t getEstimatedResamplerAvailableSamples() const;
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.
// 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;
};
+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[1].data(), channelBuffers[1].size() }
};
const std::size_t sampleCount{ decoder->readSamples(outputBuffers, sampleCountPerChannel) };
const std::size_t sampleCount{ decoder->readSamples(outputBuffers) };
totalSampleCount += sampleCount;
}