Simplified interface, optim to read directly from the resampler if enough samples are available
This commit is contained in:
+1
-1
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user