Replaced SoundFileReaderWav implementation with miniaudio (dr_)wav decoder.

This commit is contained in:
binary1248 2024-05-17 03:59:35 +02:00 committed by Lukas Dürrenberger
parent 002b8953fa
commit 1a40f01957
5 changed files with 186 additions and 374 deletions

View File

@ -84,7 +84,7 @@ sfml_add_library(Audio
target_compile_definitions(sfml-audio PRIVATE OV_EXCLUDE_STATIC_CALLBACKS FLAC__NO_DLL)
# disable miniaudio features we do not use
target_compile_definitions(sfml-audio PRIVATE MA_NO_DECODING MA_NO_ENCODING MA_NO_RESOURCE_MANAGER MA_NO_GENERATION)
target_compile_definitions(sfml-audio PRIVATE MA_NO_MP3 MA_NO_FLAC MA_NO_ENCODING MA_NO_RESOURCE_MANAGER MA_NO_GENERATION)
# setup dependencies
target_link_libraries(sfml-audio

View File

@ -184,6 +184,56 @@ ma_channel MiniaudioUtils::soundChannelToMiniaudioChannel(SoundChannel soundChan
}
////////////////////////////////////////////////////////////
SoundChannel MiniaudioUtils::miniaudioChannelToSoundChannel(ma_channel soundChannel)
{
switch (soundChannel)
{
case MA_CHANNEL_NONE:
return SoundChannel::Unspecified;
case MA_CHANNEL_MONO:
return SoundChannel::Mono;
case MA_CHANNEL_FRONT_LEFT:
return SoundChannel::FrontLeft;
case MA_CHANNEL_FRONT_RIGHT:
return SoundChannel::FrontRight;
case MA_CHANNEL_FRONT_CENTER:
return SoundChannel::FrontCenter;
case MA_CHANNEL_FRONT_LEFT_CENTER:
return SoundChannel::FrontLeftOfCenter;
case MA_CHANNEL_FRONT_RIGHT_CENTER:
return SoundChannel::FrontRightOfCenter;
case MA_CHANNEL_LFE:
return SoundChannel::LowFrequencyEffects;
case MA_CHANNEL_BACK_LEFT:
return SoundChannel::BackLeft;
case MA_CHANNEL_BACK_RIGHT:
return SoundChannel::BackRight;
case MA_CHANNEL_BACK_CENTER:
return SoundChannel::BackCenter;
case MA_CHANNEL_SIDE_LEFT:
return SoundChannel::SideLeft;
case MA_CHANNEL_SIDE_RIGHT:
return SoundChannel::SideRight;
case MA_CHANNEL_TOP_CENTER:
return SoundChannel::TopCenter;
case MA_CHANNEL_TOP_FRONT_LEFT:
return SoundChannel::TopFrontLeft;
case MA_CHANNEL_TOP_FRONT_RIGHT:
return SoundChannel::TopFrontRight;
case MA_CHANNEL_TOP_FRONT_CENTER:
return SoundChannel::TopFrontCenter;
case MA_CHANNEL_TOP_BACK_LEFT:
return SoundChannel::TopBackLeft;
case MA_CHANNEL_TOP_BACK_RIGHT:
return SoundChannel::TopBackRight;
default:
assert(soundChannel == MA_CHANNEL_TOP_BACK_CENTER);
return SoundChannel::TopBackCenter;
}
}
////////////////////////////////////////////////////////////
Time MiniaudioUtils::getPlayingOffset(ma_sound& sound)
{

View File

@ -44,9 +44,10 @@ class Time;
namespace priv::MiniaudioUtils
{
[[nodiscard]] ma_channel soundChannelToMiniaudioChannel(SoundChannel soundChannel);
[[nodiscard]] Time getPlayingOffset(ma_sound& sound);
[[nodiscard]] ma_uint64 getFrameIndex(ma_sound& sound, Time timeOffset);
[[nodiscard]] ma_channel soundChannelToMiniaudioChannel(SoundChannel soundChannel);
[[nodiscard]] SoundChannel miniaudioChannelToSoundChannel(ma_channel soundChannel);
[[nodiscard]] Time getPlayingOffset(ma_sound& sound);
[[nodiscard]] ma_uint64 getFrameIndex(ma_sound& sound, Time timeOffset);
void reinitializeSound(ma_sound& sound, const std::function<void()>& initializeFn);
void initializeSound(const ma_data_source_vtable& vtable,

View File

@ -25,82 +25,64 @@
////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include <SFML/Audio/MiniaudioUtils.hpp>
#include <SFML/Audio/SoundFileReaderWav.hpp>
#include <SFML/System/Err.hpp>
#include <SFML/System/InputStream.hpp>
#include <SFML/System/Utils.hpp>
#include <array>
#include <ostream>
#include <vector>
#include <cassert>
#include <cstddef>
#include <cstring>
namespace
{
// The following functions read integers as little endian and
// return them in the host byte order
bool decode(sf::InputStream& stream, std::uint8_t& value)
ma_result onRead(ma_decoder* decoder, void* buffer, size_t bytesToRead, size_t* bytesRead)
{
return static_cast<std::size_t>(stream.read(&value, sizeof(value))) == sizeof(value);
auto* stream = static_cast<sf::InputStream*>(decoder->pUserData);
const auto count = stream->read(buffer, static_cast<std::int64_t>(bytesToRead));
if (count < 0)
return MA_ERROR;
*bytesRead = static_cast<size_t>(count);
return MA_SUCCESS;
}
bool decode(sf::InputStream& stream, std::int16_t& value)
ma_result onSeek(ma_decoder* decoder, ma_int64 byteOffset, ma_seek_origin origin)
{
std::byte bytes[sizeof(value)];
if (static_cast<std::size_t>(stream.read(bytes, static_cast<std::int64_t>(sizeof(bytes)))) != sizeof(bytes))
return false;
auto* stream = static_cast<sf::InputStream*>(decoder->pUserData);
value = sf::toInteger<std::int16_t>(bytes[0], bytes[1]);
switch (origin)
{
case ma_seek_origin_start:
{
if (stream->seek(byteOffset) < 0)
return MA_ERROR;
return true;
return MA_SUCCESS;
}
case ma_seek_origin_current:
{
const auto currentPosition = stream->tell();
if (currentPosition < 0)
return MA_ERROR;
if (stream->seek(stream->tell() + byteOffset) < 0)
return MA_ERROR;
return MA_SUCCESS;
}
// According to miniaudio comments, ma_seek_origin_end is not used by decoders
default:
return MA_ERROR;
}
}
bool decode(sf::InputStream& stream, std::uint16_t& value)
{
std::byte bytes[sizeof(value)];
if (static_cast<std::size_t>(stream.read(bytes, static_cast<std::int64_t>(sizeof(bytes)))) != sizeof(bytes))
return false;
value = sf::toInteger<std::uint16_t>(bytes[0], bytes[1]);
return true;
}
bool decode24bit(sf::InputStream& stream, std::uint32_t& value)
{
std::byte bytes[3];
if (static_cast<std::size_t>(stream.read(bytes, static_cast<std::int64_t>(sizeof(bytes)))) != sizeof(bytes))
return false;
value = sf::toInteger<std::uint32_t>(bytes[0], bytes[1], bytes[2]);
return true;
}
bool decode(sf::InputStream& stream, std::uint32_t& value)
{
std::byte bytes[sizeof(value)];
if (static_cast<std::size_t>(stream.read(bytes, static_cast<std::int64_t>(sizeof(bytes)))) != sizeof(bytes))
return false;
value = sf::toInteger<std::uint32_t>(bytes[0], bytes[1], bytes[2], bytes[3]);
return true;
}
const std::uint64_t mainChunkSize = 12;
const std::uint16_t waveFormatPcm = 1;
const std::uint16_t waveFormatExtensible = 65534;
const char* waveSubformatPcm =
"\x01\x00\x00\x00\x00\x00\x10\x00"
"\x80\x00\x00\xAA\x00\x38\x9B\x71";
} // namespace
namespace sf::priv
@ -108,331 +90,114 @@ namespace sf::priv
////////////////////////////////////////////////////////////
bool SoundFileReaderWav::check(InputStream& stream)
{
char header[mainChunkSize];
if (stream.read(header, sizeof(header)) < static_cast<std::int64_t>(sizeof(header)))
return false;
auto config = ma_decoder_config_init_default();
config.encodingFormat = ma_encoding_format_wav;
config.format = ma_format_s16;
ma_decoder decoder{};
return (header[0] == 'R') && (header[1] == 'I') && (header[2] == 'F') && (header[3] == 'F') && (header[8] == 'W') &&
(header[9] == 'A') && (header[10] == 'V') && (header[11] == 'E');
if (ma_decoder_init(&onRead, &onSeek, &stream, &config, &decoder) == MA_SUCCESS)
{
ma_decoder_uninit(&decoder);
return true;
}
return false;
}
////////////////////////////////////////////////////////////
SoundFileReaderWav::~SoundFileReaderWav()
{
if (m_decoder)
{
if (const ma_result result = ma_decoder_uninit(&*m_decoder); result != MA_SUCCESS)
err() << "Failed to uninitialize wav decoder: " << ma_result_description(result) << std::endl;
}
}
////////////////////////////////////////////////////////////
std::optional<SoundFileReader::Info> SoundFileReaderWav::open(InputStream& stream)
{
m_stream = &stream;
if (m_decoder)
{
if (const ma_result result = ma_decoder_uninit(&*m_decoder); result != MA_SUCCESS)
{
err() << "Failed to uninitialize wav decoder: " << ma_result_description(result) << std::endl;
return std::nullopt;
}
}
else
{
m_decoder.emplace();
}
auto info = parseHeader();
if (!info)
err() << "Failed to open WAV sound file (invalid or unsupported file)" << std::endl;
auto config = ma_decoder_config_init_default();
config.encodingFormat = ma_encoding_format_wav;
config.format = ma_format_s16;
return info;
if (const ma_result result = ma_decoder_init(&onRead, &onSeek, &stream, &config, &*m_decoder); result != MA_SUCCESS)
{
err() << "Failed to initialize wav decoder: " << ma_result_description(result) << std::endl;
m_decoder = std::nullopt;
return std::nullopt;
}
ma_uint64 frameCount{};
if (const ma_result result = ma_decoder_get_available_frames(&*m_decoder, &frameCount); result != MA_SUCCESS)
{
err() << "Failed to get available frames from wav decoder: " << ma_result_description(result) << std::endl;
return std::nullopt;
}
auto format = ma_format_unknown;
ma_uint32 sampleRate{};
std::array<ma_channel, 20> channelMap{};
if (const ma_result result = ma_decoder_get_data_format(&*m_decoder,
&format,
&m_channelCount,
&sampleRate,
channelMap.data(),
channelMap.size());
result != MA_SUCCESS)
{
err() << "Failed to get data format from wav decoder: " << ma_result_description(result) << std::endl;
return std::nullopt;
}
std::vector<SoundChannel> soundChannels;
soundChannels.reserve(m_channelCount);
for (auto i = 0u; i < m_channelCount; ++i)
soundChannels.emplace_back(priv::MiniaudioUtils::miniaudioChannelToSoundChannel(channelMap[i]));
return Info{frameCount * m_channelCount, m_channelCount, sampleRate, std::move(soundChannels)};
}
////////////////////////////////////////////////////////////
void SoundFileReaderWav::seek(std::uint64_t sampleOffset)
{
assert(m_stream && "Input stream cannot be null. Call SoundFileReaderWav::open() to initialize it.");
assert(m_decoder && "wav decoder not initialized. Call SoundFileReaderWav::open() to initialize it.");
if (m_stream->seek(static_cast<std::int64_t>(m_dataStart + sampleOffset * m_bytesPerSample) == -1))
err() << "Failed to seek WAV sound stream" << std::endl;
if (const ma_result result = ma_decoder_seek_to_pcm_frame(&*m_decoder, sampleOffset / m_channelCount);
result != MA_SUCCESS)
err() << "Failed to seek wav sound stream: " << ma_result_description(result) << std::endl;
}
////////////////////////////////////////////////////////////
std::uint64_t SoundFileReaderWav::read(std::int16_t* samples, std::uint64_t maxCount)
{
assert(m_stream && "Input stream cannot be null. Call SoundFileReaderWav::open() to initialize it.");
assert(m_decoder && "wav decoder not initialized. Call SoundFileReaderWav::open() to initialize it.");
std::uint64_t count = 0;
const auto startPos = static_cast<std::uint64_t>(m_stream->tell());
ma_uint64 framesRead{};
// Tracking of m_dataEnd is important to prevent sf::Music from reading
// data until EOF, as WAV files may have metadata at the end.
while ((count < maxCount) && (startPos + count * m_bytesPerSample < m_dataEnd))
{
switch (m_bytesPerSample)
{
case 1:
{
std::uint8_t sample = 0;
if (decode(*m_stream, sample))
*samples++ = static_cast<std::int16_t>((static_cast<std::int16_t>(sample) - 128) << 8);
else
return count;
break;
}
if (const ma_result result = ma_decoder_read_pcm_frames(&*m_decoder, samples, maxCount / m_channelCount, &framesRead);
result != MA_SUCCESS)
err() << "Failed to read from wav sound stream: " << ma_result_description(result) << std::endl;
case 2:
{
std::int16_t sample = 0;
if (decode(*m_stream, sample))
*samples++ = sample;
else
return count;
break;
}
case 3:
{
std::uint32_t sample = 0;
if (decode24bit(*m_stream, sample))
*samples++ = static_cast<std::int16_t>(sample >> 8);
else
return count;
break;
}
case 4:
{
std::uint32_t sample = 0;
if (decode(*m_stream, sample))
*samples++ = static_cast<std::int16_t>(sample >> 16);
else
return count;
break;
}
default:
{
assert(false && "Invalid bytes per sample. Must be 1, 2, 3, or 4.");
return 0;
}
}
++count;
}
return count;
}
////////////////////////////////////////////////////////////
std::optional<SoundFileReader::Info> SoundFileReaderWav::parseHeader()
{
assert(m_stream && "Input stream cannot be null. Call SoundFileReaderWav::open() to initialize it.");
// If we are here, it means that the first part of the header
// (the format) has already been checked
char mainChunk[mainChunkSize];
if (static_cast<std::size_t>(m_stream->read(mainChunk, static_cast<std::int64_t>(sizeof(mainChunk)))) !=
sizeof(mainChunk))
return std::nullopt;
// Parse all the sub-chunks
Info info;
bool dataChunkFound = false;
while (!dataChunkFound)
{
// Parse the sub-chunk id and size
char subChunkId[4];
if (static_cast<std::size_t>(m_stream->read(subChunkId, static_cast<std::int64_t>(sizeof(subChunkId)))) !=
sizeof(subChunkId))
return std::nullopt;
std::uint32_t subChunkSize = 0;
if (!decode(*m_stream, subChunkSize))
return std::nullopt;
const std::int64_t subChunkStart = m_stream->tell();
if (subChunkStart == -1)
return std::nullopt;
// Check which chunk it is
if ((subChunkId[0] == 'f') && (subChunkId[1] == 'm') && (subChunkId[2] == 't') && (subChunkId[3] == ' '))
{
// "fmt" chunk
// Audio format
std::uint16_t format = 0;
if (!decode(*m_stream, format))
return std::nullopt;
if ((format != waveFormatPcm) && (format != waveFormatExtensible))
return std::nullopt;
// Channel count
std::uint16_t channelCount = 0;
if (!decode(*m_stream, channelCount))
return std::nullopt;
info.channelCount = channelCount;
// Sample rate
std::uint32_t sampleRate = 0;
if (!decode(*m_stream, sampleRate))
return std::nullopt;
info.sampleRate = sampleRate;
// Byte rate
std::uint32_t byteRate = 0;
if (!decode(*m_stream, byteRate))
return std::nullopt;
// Block align
std::uint16_t blockAlign = 0;
if (!decode(*m_stream, blockAlign))
return std::nullopt;
// Bits per sample
std::uint16_t bitsPerSample = 0;
if (!decode(*m_stream, bitsPerSample))
return std::nullopt;
if (bitsPerSample != 8 && bitsPerSample != 16 && bitsPerSample != 24 && bitsPerSample != 32)
{
err() << "Unsupported sample size: " << bitsPerSample
<< " bit (Supported sample sizes are 8/16/24/32 bit)" << std::endl;
return std::nullopt;
}
m_bytesPerSample = bitsPerSample / 8;
if (format == waveFormatExtensible)
{
// Extension size
std::uint16_t extensionSize = 0;
if (!decode(*m_stream, extensionSize))
return std::nullopt;
// Valid bits per sample
std::uint16_t validBitsPerSample = 0;
if (!decode(*m_stream, validBitsPerSample))
return std::nullopt;
// Channel mask
std::uint32_t channelMask = 0;
if (!decode(*m_stream, channelMask))
return std::nullopt;
// NOLINTBEGIN(readability-identifier-naming)
// For WAVE channel mapping refer to: https://learn.microsoft.com/en-us/previous-versions/windows/hardware/design/dn653308(v=vs.85)#default-channel-ordering
static constexpr auto SPEAKER_FRONT_LEFT = 0x1u;
static constexpr auto SPEAKER_FRONT_RIGHT = 0x2u;
static constexpr auto SPEAKER_FRONT_CENTER = 0x4u;
static constexpr auto SPEAKER_LOW_FREQUENCY = 0x8u;
static constexpr auto SPEAKER_BACK_LEFT = 0x10u;
static constexpr auto SPEAKER_BACK_RIGHT = 0x20u;
static constexpr auto SPEAKER_FRONT_LEFT_OF_CENTER = 0x40u;
static constexpr auto SPEAKER_FRONT_RIGHT_OF_CENTER = 0x80u;
static constexpr auto SPEAKER_BACK_CENTER = 0x100u;
static constexpr auto SPEAKER_SIDE_LEFT = 0x200u;
static constexpr auto SPEAKER_SIDE_RIGHT = 0x400u;
static constexpr auto SPEAKER_TOP_CENTER = 0x800u;
static constexpr auto SPEAKER_TOP_FRONT_LEFT = 0x1000u;
static constexpr auto SPEAKER_TOP_FRONT_CENTER = 0x2000u;
static constexpr auto SPEAKER_TOP_FRONT_RIGHT = 0x4000u;
static constexpr auto SPEAKER_TOP_BACK_LEFT = 0x8000u;
static constexpr auto SPEAKER_TOP_BACK_CENTER = 0x10000u;
static constexpr auto SPEAKER_TOP_BACK_RIGHT = 0x20000u;
// NOLINTEND(readability-identifier-naming)
info.channelMap.clear();
const auto checkChannel = [channelMask, &info](auto bit, auto soundChannel)
{
if ((channelMask & bit) != 0)
info.channelMap.push_back(soundChannel);
};
checkChannel(SPEAKER_FRONT_LEFT, SoundChannel::FrontLeft);
checkChannel(SPEAKER_FRONT_RIGHT, SoundChannel::FrontRight);
checkChannel(SPEAKER_FRONT_CENTER, SoundChannel::FrontCenter);
checkChannel(SPEAKER_LOW_FREQUENCY, SoundChannel::LowFrequencyEffects);
checkChannel(SPEAKER_BACK_LEFT, SoundChannel::BackLeft);
checkChannel(SPEAKER_BACK_RIGHT, SoundChannel::BackRight);
checkChannel(SPEAKER_FRONT_LEFT_OF_CENTER, SoundChannel::FrontLeftOfCenter);
checkChannel(SPEAKER_FRONT_RIGHT_OF_CENTER, SoundChannel::FrontRightOfCenter);
checkChannel(SPEAKER_BACK_CENTER, SoundChannel::BackCenter);
checkChannel(SPEAKER_SIDE_LEFT, SoundChannel::SideLeft);
checkChannel(SPEAKER_SIDE_RIGHT, SoundChannel::SideRight);
checkChannel(SPEAKER_TOP_CENTER, SoundChannel::TopCenter);
checkChannel(SPEAKER_TOP_FRONT_LEFT, SoundChannel::TopFrontLeft);
checkChannel(SPEAKER_TOP_FRONT_CENTER, SoundChannel::TopFrontCenter);
checkChannel(SPEAKER_TOP_FRONT_RIGHT, SoundChannel::TopFrontRight);
checkChannel(SPEAKER_TOP_BACK_LEFT, SoundChannel::TopBackLeft);
checkChannel(SPEAKER_TOP_BACK_CENTER, SoundChannel::TopBackCenter);
checkChannel(SPEAKER_TOP_BACK_RIGHT, SoundChannel::TopBackRight);
assert(info.channelCount == info.channelMap.size());
if (info.channelCount != info.channelMap.size())
{
err() << "WAV sound file channel count does not match number of set bits in channel mask" << std::endl;
return std::nullopt;
}
// Subformat
char subformat[16];
if (static_cast<std::size_t>(m_stream->read(subformat, static_cast<std::int64_t>(sizeof(subformat)))) !=
sizeof(subformat))
return std::nullopt;
if (std::memcmp(subformat, waveSubformatPcm, sizeof(subformat)) != 0)
{
err() << "Unsupported format: extensible format with non-PCM subformat" << std::endl;
return std::nullopt;
}
if (validBitsPerSample != bitsPerSample)
{
err() << "Unsupported format: sample size (" << validBitsPerSample
<< " bits) and "
"sample container size ("
<< bitsPerSample << " bits) differ" << std::endl;
return std::nullopt;
}
}
else
{
// If we don't have a waveFormatExtensible header, fill the channel map based on a best guess for mono/stereo
info.channelMap.clear();
if (info.channelCount == 0)
{
err() << "WAV sound file channel count 0" << std::endl;
return std::nullopt;
}
else if (info.channelCount == 1)
{
info.channelMap.push_back(SoundChannel::Mono);
}
else if (info.channelCount == 2)
{
info.channelMap.push_back(SoundChannel::FrontLeft);
info.channelMap.push_back(SoundChannel::FrontRight);
}
else
{
info.channelMap.push_back(SoundChannel::FrontLeft);
info.channelMap.push_back(SoundChannel::FrontRight);
for (auto i = 2u; i < info.channelCount; ++i)
info.channelMap.push_back(SoundChannel::Unspecified);
}
}
// Skip potential extra information
if (m_stream->seek(subChunkStart + subChunkSize) == -1)
return std::nullopt;
}
else if ((subChunkId[0] == 'd') && (subChunkId[1] == 'a') && (subChunkId[2] == 't') && (subChunkId[3] == 'a'))
{
// "data" chunk
// Compute the total number of samples
info.sampleCount = subChunkSize / m_bytesPerSample;
// Store the start and end position of samples in the file
m_dataStart = static_cast<std::uint64_t>(subChunkStart);
m_dataEnd = m_dataStart + info.sampleCount * m_bytesPerSample;
dataChunkFound = true;
}
else
{
// unknown chunk, skip it
if (m_stream->seek(m_stream->tell() + subChunkSize) == -1)
return std::nullopt;
}
}
return info;
return framesRead * m_channelCount;
}
} // namespace sf::priv

View File

@ -29,6 +29,8 @@
////////////////////////////////////////////////////////////
#include <SFML/Audio/SoundFileReader.hpp>
#include <miniaudio.h>
#include <optional>
#include <cstdint>
@ -58,6 +60,12 @@ public:
////////////////////////////////////////////////////////////
[[nodiscard]] static bool check(InputStream& stream);
////////////////////////////////////////////////////////////
/// \brief Destructor
///
////////////////////////////////////////////////////////////
~SoundFileReaderWav() override;
////////////////////////////////////////////////////////////
/// \brief Open a sound file for reading
///
@ -95,23 +103,11 @@ public:
[[nodiscard]] std::uint64_t read(std::int16_t* samples, std::uint64_t maxCount) override;
private:
////////////////////////////////////////////////////////////
/// \brief Read the header of the open file
///
/// \param info Attributes of the sound file
///
/// \return True on success, false on error
///
////////////////////////////////////////////////////////////
std::optional<Info> parseHeader();
////////////////////////////////////////////////////////////
// Member data
////////////////////////////////////////////////////////////
InputStream* m_stream{}; //!< Source stream to read from
unsigned int m_bytesPerSample{}; //!< Size of a sample, in bytes
std::uint64_t m_dataStart{}; //!< Starting position of the audio data in the open file
std::uint64_t m_dataEnd{}; //!< Position one byte past the end of the audio data in the open file
std::optional<ma_decoder> m_decoder; //!< wav decoder
ma_uint32 m_channelCount{}; //!< Number of channels
};
} // namespace sf::priv