Fixed crash with some corrupted files, ref #857

This commit is contained in:
emeric
2026-06-21 13:35:50 +02:00
parent 52b6af434d
commit eca418d2bc
2 changed files with 116 additions and 64 deletions
+103 -64
View File
@@ -67,6 +67,37 @@ namespace lms::audio::ffmpeg
throw Exception("Unsupported PcmSampleType"); throw Exception("Unsupported PcmSampleType");
} }
SwrContextPtr createResampler(const PcmParameters& params, const AVChannelLayout& inLayout, AVSampleFormat inFmt, int inSampleRate)
{
const ::AVSampleFormat outFmt{ toAvSampleFormat(params.sampleType, params.planar) };
AVChannelLayout outLayout;
::av_channel_layout_default(&outLayout, static_cast<int>(params.channelCount));
::SwrContext* context{};
const int err{ ::swr_alloc_set_opts2(
&context,
&outLayout,
outFmt,
static_cast<int>(params.sampleRate),
&inLayout,
inFmt,
inSampleRate,
0,
nullptr) };
::av_channel_layout_uninit(&outLayout);
if (err < 0 || !context)
throw FFmpegException{ "Cannot allocate resampler context", err };
SwrContextPtr resampleContext{ context };
const int initErr{ ::swr_init(resampleContext.get()) };
if (initErr < 0)
throw FFmpegException{ "Cannot initialize resampler", initErr };
return resampleContext;
}
} // namespace } // namespace
PcmDecoder::PcmDecoder(const std::filesystem::path& filePath, std::chrono::microseconds offset, const PcmParameters& parameters) PcmDecoder::PcmDecoder(const std::filesystem::path& filePath, std::chrono::microseconds offset, const PcmParameters& parameters)
@@ -159,36 +190,14 @@ namespace lms::audio::ffmpeg
if (!_inputPacket) if (!_inputPacket)
throw Exception{ "Cannot allocate input packet" }; throw Exception{ "Cannot allocate input packet" };
// Resampler _resampleContext = createResampler(_parameters, _decoderContext->ch_layout, _decoderContext->sample_fmt, _decoderContext->sample_rate);
const ::AVSampleFormat outFmt{ toAvSampleFormat(_parameters.sampleType, _parameters.planar) }; _resamplerInputConfig = {
AVChannelLayout outLayout; _decoderContext->sample_rate,
::av_channel_layout_default(&outLayout, _parameters.channelCount); static_cast<int>(_decoderContext->sample_fmt),
_decoderContext->ch_layout.nb_channels,
{ static_cast<int>(_decoderContext->ch_layout.order),
::SwrContext* context{}; _decoderContext->ch_layout.u.mask,
::swr_alloc_set_opts2( };
&context, // existing context
&outLayout, // out layout
outFmt, // out format
static_cast<int>(_parameters.sampleRate), // out rate
&_decoderContext->ch_layout, // in layout
_decoderContext->sample_fmt, // in format
_decoderContext->sample_rate, // in rate
0, // log offset
nullptr);
::av_channel_layout_uninit(&outLayout);
if (!context)
throw Exception{ "Cannot allocate resampler context" };
_resampleContext = SwrContextPtr{ context };
}
{
int error{ ::swr_init(_resampleContext.get()) };
if (error < 0)
throw FFmpegException{ "Cannot initialize resampler", error };
}
} }
PcmDecoder::~PcmDecoder() = default; PcmDecoder::~PcmDecoder() = default;
@@ -232,34 +241,28 @@ namespace lms::audio::ffmpeg
} }
else else
{ {
std::array<uint8_t*, AV_NUM_DATA_POINTERS> outData{}; const std::size_t outSampleCount{ resampleFrame(outputChannelBuffers, maxSamplesPerChannel, _decodedFrame.get()) };
for (std::size_t i{}; i < outputChannelBuffers.size(); ++i)
outData[i] = reinterpret_cast<uint8_t*>(outputChannelBuffers[i].data());
// Resample decoded audio
const int outSampleCount{ ::swr_convert(
_resampleContext.get(),
outData.data(),
static_cast<int>(maxSamplesPerChannel),
(const uint8_t**)_decodedFrame->data,
_decodedFrame->nb_samples) };
::av_frame_unref(_decodedFrame.get()); ::av_frame_unref(_decodedFrame.get());
if (outSampleCount < 0)
throw FFmpegException{ "swr_convert failed", outSampleCount };
if (outSampleCount > 0) if (outSampleCount > 0)
return static_cast<std::size_t>(outSampleCount); return outSampleCount;
continue; // Rare but legal: frame produced no output (delay accumulation) continue; // delay accumulation: resampler is buffering, no output yet
} }
// Drain resampler once decoder is drained // Drain resampler once decoder is drained
if (_draining) if (_draining)
{ {
const std::size_t outSampleCount{ drainResampler(outputChannelBuffers, maxSamplesPerChannel) }; try
if (outSampleCount > 0) {
return outSampleCount; const std::size_t outSampleCount{ drainResampler(outputChannelBuffers, maxSamplesPerChannel) };
if (outSampleCount > 0)
return outSampleCount;
}
catch (const Exception& e)
{
LMS_LOG(AUDIO, ERROR, "Failed to drain resampler: " << e.what());
}
_finished = true; _finished = true;
break; break;
@@ -286,7 +289,6 @@ namespace lms::audio::ffmpeg
if (outputChannelBuffers.size() != _parameters.channelCount) if (outputChannelBuffers.size() != _parameters.channelCount)
throw Exception{ "Expected " + std::to_string(_parameters.channelCount) + " buffers for planar output" }; 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)) }; const int bytesPerSample{ av_get_bytes_per_sample(toAvSampleFormat(_parameters.sampleType, true)) };
if (bytesPerSample <= 0) if (bytesPerSample <= 0)
throw Exception{ "Invalid bytes per sample for output format" }; throw Exception{ "Invalid bytes per sample for output format" };
@@ -306,7 +308,6 @@ namespace lms::audio::ffmpeg
if (bytesPerSample <= 0) if (bytesPerSample <= 0)
throw Exception{ "Invalid bytes per sample for output format" }; 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); const std::size_t sampleCount = outputChannelBuffers[0].size() / (bytesPerSample * _parameters.channelCount);
return sampleCount; return sampleCount;
@@ -353,28 +354,66 @@ namespace lms::audio::ffmpeg
} }
} }
std::size_t PcmDecoder::drainResampler(std::span<WritableBuffer> outputChannelBuffers, std::size_t maxSamplesPerChannel) bool PcmDecoder::inputFormatChanged(const AVFrame* frame) const
{ {
std::array<uint8_t*, AV_NUM_DATA_POINTERS> outData{}; const auto& c{ _resamplerInputConfig };
for (std::size_t i{}; i < outputChannelBuffers.size(); ++i) if (frame->sample_rate != c.sampleRate
outData[i] = reinterpret_cast<uint8_t*>(outputChannelBuffers[i].data()); || frame->format != c.sampleFormat
|| frame->ch_layout.nb_channels != c.nbChannels
|| static_cast<int>(frame->ch_layout.order) != c.channelOrder)
return true;
if (frame->ch_layout.order == AV_CHANNEL_ORDER_NATIVE)
return frame->ch_layout.u.mask != c.channelMask;
return false;
}
const int outSampleCount{ ::swr_convert(_resampleContext.get(), void PcmDecoder::reinitResamplerForFrame(const AVFrame* frame)
outData.data(), {
static_cast<int>(maxSamplesPerChannel), // The format change is caused by a corrupt/non-standard frame so the lost samples are likely garbled audio anyway.
nullptr, _resampleContext = createResampler(_parameters, frame->ch_layout, static_cast<AVSampleFormat>(frame->format), frame->sample_rate);
0) }; _resamplerInputConfig = {
frame->sample_rate,
frame->format,
frame->ch_layout.nb_channels,
static_cast<int>(frame->ch_layout.order),
frame->ch_layout.u.mask,
};
}
std::size_t PcmDecoder::resampleFrame(std::span<WritableBuffer> outputChannelBuffers, std::size_t maxSamplesPerChannel, const AVFrame* inputFrame)
{
if (inputFrame && inputFormatChanged(inputFrame))
{
LMS_LOG(AUDIO, DEBUG, "Input format changed");
reinitResamplerForFrame(inputFrame);
}
std::array<std::uint8_t*, AV_NUM_DATA_POINTERS> outData{};
for (std::size_t i{}; i < outputChannelBuffers.size(); ++i)
outData[i] = static_cast<std::uint8_t*>(static_cast<void*>(outputChannelBuffers[i].data()));
const int outSampleCount{ ::swr_convert(
_resampleContext.get(),
outData.data(),
static_cast<int>(maxSamplesPerChannel),
inputFrame ? reinterpret_cast<const std::uint8_t* const*>(inputFrame->data) : nullptr,
inputFrame ? inputFrame->nb_samples : 0) };
if (outSampleCount < 0) if (outSampleCount < 0)
throw FFmpegException{ "swr_convert (drain) failed", outSampleCount }; throw FFmpegException{ inputFrame ? "swr_convert failed" : "swr_convert (drain) failed", outSampleCount };
return outSampleCount; return static_cast<std::size_t>(outSampleCount);
}
std::size_t PcmDecoder::drainResampler(std::span<WritableBuffer> outputChannelBuffers, std::size_t maxSamplesPerChannel)
{
return resampleFrame(outputChannelBuffers, maxSamplesPerChannel, nullptr);
} }
std::size_t PcmDecoder::getEstimatedResamplerAvailableSamples() const std::size_t PcmDecoder::getEstimatedResamplerAvailableSamples() const
{ {
const int64_t delayedInputSampleCount{ ::swr_get_delay(_resampleContext.get(), _decoderContext->sample_rate) }; const int64_t delayedInputSampleCount{ ::swr_get_delay(_resampleContext.get(), _resamplerInputConfig.sampleRate) };
const int64_t sampleCount{ av_rescale_rnd(delayedInputSampleCount, _parameters.sampleRate, _decoderContext->sample_rate, AV_ROUND_UP) }; const int64_t sampleCount{ av_rescale_rnd(delayedInputSampleCount, _parameters.sampleRate, _resamplerInputConfig.sampleRate, AV_ROUND_UP) };
return sampleCount; return sampleCount;
} }
+13
View File
@@ -44,6 +44,9 @@ namespace lms::audio::ffmpeg
std::size_t computeSampleCountPerChannel(std::span<WritableBuffer> outputChannelBuffers) const; std::size_t computeSampleCountPerChannel(std::span<WritableBuffer> outputChannelBuffers) const;
void feedDecoder(); void feedDecoder();
bool inputFormatChanged(const AVFrame* frame) const;
void reinitResamplerForFrame(const AVFrame* frame);
std::size_t resampleFrame(std::span<WritableBuffer> outputChannelBuffers, std::size_t maxSamplesPerChannel, const AVFrame* inputFrame);
std::size_t drainResampler(std::span<WritableBuffer> outputChannelBuffers, std::size_t maxSamplesPerChannel); std::size_t drainResampler(std::span<WritableBuffer> outputChannelBuffers, std::size_t maxSamplesPerChannel);
std::size_t getEstimatedResamplerAvailableSamples() const; std::size_t getEstimatedResamplerAvailableSamples() const;
@@ -60,5 +63,15 @@ namespace lms::audio::ffmpeg
AVFramePtr _decodedFrame; AVFramePtr _decodedFrame;
AVPacketPtr _inputPacket; AVPacketPtr _inputPacket;
SwrContextPtr _resampleContext; SwrContextPtr _resampleContext;
struct ResamplerInputConfig
{
int sampleRate{};
int sampleFormat{};
int nbChannels{};
int channelOrder{};
std::uint64_t channelMask{};
};
ResamplerInputConfig _resamplerInputConfig;
}; };
} // namespace lms::audio::ffmpeg } // namespace lms::audio::ffmpeg