diff --git a/src/C4AudioSystemSdl.cpp b/src/C4AudioSystemSdl.cpp index fd8f24f02..b0c474016 100644 --- a/src/C4AudioSystemSdl.cpp +++ b/src/C4AudioSystemSdl.cpp @@ -55,7 +55,13 @@ class C4AudioSystemSdl : public C4AudioSystem std::optional system; - static void ThrowIfFailed(const char *funcName, bool failed); + static void ThrowIfFailed(const char *funcName, bool failed, std::string_view errorMessage = {}); + + template + using SampleLoadFunc = T *(*)(SDL_RWops *, int); + + template + static T *LoadSampleCheckMpegLayer3Header(SampleLoadFunc loadFunc, const char* funcName, const void *const buf, const std::size_t size); public: @@ -140,11 +146,16 @@ C4AudioSystemSdl::C4AudioSystemSdl(const int maxChannels, const bool preferLinea this->system.emplace(std::move(system)); } -void C4AudioSystemSdl::ThrowIfFailed(const char *const funcName, const bool failed) +void C4AudioSystemSdl::ThrowIfFailed(const char *const funcName, const bool failed, std::string_view errorMessage) { if (failed) { - throw std::runtime_error{std::format("SDL_mixer: {} failed: {}", funcName, Mix_GetError())}; + if (errorMessage.empty()) + { + errorMessage = Mix_GetError(); + } + + throw std::runtime_error{std::format("SDL_mixer: {} failed: {}", funcName, errorMessage)}; } } @@ -175,18 +186,68 @@ void C4AudioSystemSdl::StopMusic() void C4AudioSystemSdl::UnpauseMusic() { /* Not supported */ } -C4AudioSystemSdl::MusicFileSdl::MusicFileSdl(const void *const buf, const std::size_t size) - : sample{Mix_LoadMUS_RW(SDL_RWFromConstMem(buf, size), SDL_TRUE)} -{ - ThrowIfFailed("Mix_LoadMUS_RW", !sample); +namespace { + template + using SampleLoadFunc = T *(*)(SDL_RWops *, int); } -C4AudioSystemSdl::SoundFileSdl::SoundFileSdl(const void *const buf, const std::size_t size) - : sample{Mix_LoadWAV_RW(SDL_RWFromConstMem(buf, size), SDL_TRUE)} +template +T *C4AudioSystemSdl::LoadSampleCheckMpegLayer3Header(SampleLoadFunc loadFunc, const char* funcName, const void *const buf, const std::size_t size) { - ThrowIfFailed("Mix_LoadWAV_RW", !sample); + const auto direct = loadFunc(SDL_RWFromConstMem(buf, size), SDL_TRUE); + if (direct) + { + return direct; + } + const std::string error{Mix_GetError()}; + + // According to http://www.idea2ic.com/File_Formats/MPEG%20Audio%20Frame%20Header.pdf + // Maximum possible frame size = 144 * max bit rate / min sample rate + padding + // chosen values are limited to layer 3 + constexpr int MaxFrameSize = 144 * 320'000 / 8'000 + 1; + + std::string_view data{reinterpret_cast(buf), size}; + for (std::size_t i = 0; i < data.size() - 4; ++i) + { + // first 8 of 11 frame sync bits + if (data[i] != static_cast(0xFF)) continue; + + const auto byte2 = data[i + 1]; + // rest of the sync bits + check for Layer 3 (SDL_mixer only accepts layer 3) + if ((byte2 & 0xE6) != 0xE2) continue; + // MPEG version bit value 01 is reserved + if ((byte2 & 0x18) == 0x08) continue; + + const auto byte3 = data[i + 2]; + // bitrate index 1111 is invalid + if ((byte3 & 0xF0) == 0xF0) continue; + // sampling rate index 11 is reserved + if ((byte3 & 0x0C) == 0x0C) continue; + + const auto byte4 = data[i + 3]; + // emphasis bit value 10 is reserved + if ((byte4 & 0x03) == 0x02) continue; + + // at this point there seems to be a valid MPEG frame header + const auto sample = loadFunc(SDL_RWFromConstMem(data.data() + i, size - i), SDL_TRUE); + if (sample) + { + return sample; + } + } + + ThrowIfFailed(funcName, true, error); + return nullptr; } +C4AudioSystemSdl::MusicFileSdl::MusicFileSdl(const void *const buf, const std::size_t size) + : sample{LoadSampleCheckMpegLayer3Header(Mix_LoadMUS_RW, "Mix_LoadMUS_RW", buf, size)} +{} + +C4AudioSystemSdl::SoundFileSdl::SoundFileSdl(const void *const buf, const std::size_t size) + : sample{LoadSampleCheckMpegLayer3Header(Mix_LoadWAV_RW, "Mix_LoadWAV_RW", buf, size)} +{} + std::uint32_t C4AudioSystemSdl::SoundFileSdl::GetDuration() const { return 1000 * sample->alen / BytesPerSecond;