Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions src/audio/audio_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ std::vector<float> readMp3(const std::string_view& mp3Data, uint32_t targetSampl
if (mp3.totalPCMFrameCount != std::numeric_limits<uint64_t>::max()) {
try {
if (targetSampleRate > 0) {
validateAudioFileSize(mp3.totalPCMFrameCount, mp3.sampleRate, targetSampleRate, mp3.channels, sizeof(float));
validateAudioFileSize(mp3.totalPCMFrameCount, mp3.sampleRate, targetSampleRate, /*will be downmixed to mono*/ 1, sizeof(float));
}
} catch (...) {
drmp3_uninit(&mp3);
Expand All @@ -188,10 +188,20 @@ std::vector<float> readMp3(const std::string_view& mp3Data, uint32_t targetSampl
break;
}
if (pcmf32.size() > AUDIO_BUFFER_SIZE_LIMIT) {
drmp3_uninit(&mp3);
throw std::overflow_error("Decoded audio buffer size overflow");
}
pcmf32.insert(pcmf32.end(), tempBuffer, tempBuffer + framesRead * mp3.channels);
if (mp3.channels == 1) {
pcmf32.insert(pcmf32.end(), tempBuffer, tempBuffer + framesRead);
} else {
// Down-mix interleaved stereo to mono so that readMp3 honours the same
// "mono float32 PCM samples" contract as readWav. Done per chunk so the
// size guard below keeps measuring the final buffer.
const size_t writeOffset = pcmf32.size();
pcmf32.resize(writeOffset + framesRead);
for (drmp3_uint64 frame = 0; frame < framesRead; frame++) {
pcmf32[writeOffset + frame] = (tempBuffer[2 * frame] + tempBuffer[2 * frame + 1]) * 0.5f;
}
}
validateAudioFileSizeAgainstMaxValue(pcmf32.size() * sizeof(float));
}
} catch (...) {
Expand Down
27 changes: 23 additions & 4 deletions src/test/audio/audio_utils_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,8 @@ TEST_F(AudioUtilsSampleRateTest, mp3FileRejectedWhenExceedsMaxFileSizeEnv) {
mp3.push_back(static_cast<char>(0x40));
mp3.append(413, '\0');
std::string_view view(mp3);
// For this frame, actual decoded size is 2304 samples (stereo or decoder output)
size_t expectedDecodedSize = 2304 * sizeof(float);
// This joint-stereo frame decodes to 1152 PCM frames, returned as 1152 mono samples.
size_t expectedDecodedSize = 1152 * sizeof(float);
SetEnvironmentVar("OVMS_AUDIO_MAX_FILE_SIZE_BYTES", std::to_string(expectedDecodedSize - 1));
std::vector<float> decoded;
EXPECT_THROW({ decoded = readMp3(view); }, std::runtime_error);
Expand All @@ -187,14 +187,33 @@ TEST_F(AudioUtilsSampleRateTest, mp3FileAcceptedWhenAtMaxFileSizeEnv) {
mp3.push_back(static_cast<char>(0x40));
mp3.append(413, '\0');
std::string_view view(mp3);
// For this frame, actual decoded size is 2304 samples (stereo or decoder output)
size_t expectedDecodedSize = 2304 * sizeof(float);
// This joint-stereo frame decodes to 1152 PCM frames, returned as 1152 mono samples.
size_t expectedDecodedSize = 1152 * sizeof(float);
SetEnvironmentVar("OVMS_AUDIO_MAX_FILE_SIZE_BYTES", std::to_string(expectedDecodedSize));
std::vector<float> decoded;
EXPECT_NO_THROW({ decoded = readMp3(view); });
UnSetEnvironmentVar("OVMS_AUDIO_MAX_FILE_SIZE_BYTES");
}

// readMp3 is documented to return mono float32 PCM samples, and readWav already
// down-mixes stereo. A 2-channel MP3 must therefore yield one sample per PCM frame,
// not one per channel - otherwise the interleaved stream is read downstream as a
// mono waveform of twice the real duration.
TEST_F(AudioUtilsSampleRateTest, mp3StereoIsDownmixedToMono) {
std::string mp3;
mp3.reserve(417);
mp3.push_back(static_cast<char>(0xFF));
mp3.push_back(static_cast<char>(0xFB));
mp3.push_back(static_cast<char>(0x90));
mp3.push_back(static_cast<char>(0x40)); // channel mode 01: joint stereo
mp3.append(413, '\0');
std::string_view view(mp3);
std::vector<float> decoded;
ASSERT_NO_THROW({ decoded = readMp3(view, DISABLED_RESAMPLING_SAMPLE_RATE); });
// 1152 PCM frames from a single MPEG-1 Layer III frame, down-mixed to 1152 samples.
EXPECT_EQ(decoded.size(), 1152u);
}

// Validates that validateAudioFileSize correctly rejects when inputSamples * targetRate
// would overflow size_t. This guards against the case where dr_mp3 provides an inflated
// totalPCMFrameCount (e.g. from a malicious Xing tag or UINT64_MAX sentinel).
Expand Down