mirror of
https://github.com/SFML/SFML.git
synced 2024-12-01 07:41:05 +08:00
Replaced SoundFileReaderWav implementation with miniaudio (dr_)wav decoder.
This commit is contained in:
parent
002b8953fa
commit
1a40f01957
@ -84,7 +84,7 @@ sfml_add_library(Audio
|
|||||||
target_compile_definitions(sfml-audio PRIVATE OV_EXCLUDE_STATIC_CALLBACKS FLAC__NO_DLL)
|
target_compile_definitions(sfml-audio PRIVATE OV_EXCLUDE_STATIC_CALLBACKS FLAC__NO_DLL)
|
||||||
|
|
||||||
# disable miniaudio features we do not use
|
# 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
|
# setup dependencies
|
||||||
target_link_libraries(sfml-audio
|
target_link_libraries(sfml-audio
|
||||||
|
@ -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)
|
Time MiniaudioUtils::getPlayingOffset(ma_sound& sound)
|
||||||
{
|
{
|
||||||
|
@ -45,6 +45,7 @@ class Time;
|
|||||||
namespace priv::MiniaudioUtils
|
namespace priv::MiniaudioUtils
|
||||||
{
|
{
|
||||||
[[nodiscard]] ma_channel soundChannelToMiniaudioChannel(SoundChannel soundChannel);
|
[[nodiscard]] ma_channel soundChannelToMiniaudioChannel(SoundChannel soundChannel);
|
||||||
|
[[nodiscard]] SoundChannel miniaudioChannelToSoundChannel(ma_channel soundChannel);
|
||||||
[[nodiscard]] Time getPlayingOffset(ma_sound& sound);
|
[[nodiscard]] Time getPlayingOffset(ma_sound& sound);
|
||||||
[[nodiscard]] ma_uint64 getFrameIndex(ma_sound& sound, Time timeOffset);
|
[[nodiscard]] ma_uint64 getFrameIndex(ma_sound& sound, Time timeOffset);
|
||||||
|
|
||||||
|
@ -25,82 +25,64 @@
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
// Headers
|
// Headers
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
|
#include <SFML/Audio/MiniaudioUtils.hpp>
|
||||||
#include <SFML/Audio/SoundFileReaderWav.hpp>
|
#include <SFML/Audio/SoundFileReaderWav.hpp>
|
||||||
|
|
||||||
#include <SFML/System/Err.hpp>
|
#include <SFML/System/Err.hpp>
|
||||||
#include <SFML/System/InputStream.hpp>
|
#include <SFML/System/InputStream.hpp>
|
||||||
#include <SFML/System/Utils.hpp>
|
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
// The following functions read integers as little endian and
|
ma_result onRead(ma_decoder* decoder, void* buffer, size_t bytesToRead, size_t* bytesRead)
|
||||||
// return them in the host byte order
|
|
||||||
|
|
||||||
bool decode(sf::InputStream& stream, std::uint8_t& value)
|
|
||||||
{
|
{
|
||||||
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)];
|
auto* stream = static_cast<sf::InputStream*>(decoder->pUserData);
|
||||||
if (static_cast<std::size_t>(stream.read(bytes, static_cast<std::int64_t>(sizeof(bytes)))) != sizeof(bytes))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
value = sf::toInteger<std::int16_t>(bytes[0], bytes[1]);
|
switch (origin)
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool decode(sf::InputStream& stream, std::uint16_t& value)
|
|
||||||
{
|
{
|
||||||
std::byte bytes[sizeof(value)];
|
case ma_seek_origin_start:
|
||||||
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 (stream->seek(byteOffset) < 0)
|
||||||
if (static_cast<std::size_t>(stream.read(bytes, static_cast<std::int64_t>(sizeof(bytes)))) != sizeof(bytes))
|
return MA_ERROR;
|
||||||
return false;
|
|
||||||
|
|
||||||
value = sf::toInteger<std::uint32_t>(bytes[0], bytes[1], bytes[2]);
|
return MA_SUCCESS;
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
case ma_seek_origin_current:
|
||||||
bool decode(sf::InputStream& stream, std::uint32_t& value)
|
|
||||||
{
|
{
|
||||||
std::byte bytes[sizeof(value)];
|
const auto currentPosition = stream->tell();
|
||||||
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]);
|
if (currentPosition < 0)
|
||||||
|
return MA_ERROR;
|
||||||
|
|
||||||
return true;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
namespace sf::priv
|
namespace sf::priv
|
||||||
@ -108,331 +90,114 @@ namespace sf::priv
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
bool SoundFileReaderWav::check(InputStream& stream)
|
bool SoundFileReaderWav::check(InputStream& stream)
|
||||||
{
|
{
|
||||||
char header[mainChunkSize];
|
auto config = ma_decoder_config_init_default();
|
||||||
if (stream.read(header, sizeof(header)) < static_cast<std::int64_t>(sizeof(header)))
|
config.encodingFormat = ma_encoding_format_wav;
|
||||||
return false;
|
config.format = ma_format_s16;
|
||||||
|
ma_decoder decoder{};
|
||||||
|
|
||||||
return (header[0] == 'R') && (header[1] == 'I') && (header[2] == 'F') && (header[3] == 'F') && (header[8] == 'W') &&
|
if (ma_decoder_init(&onRead, &onSeek, &stream, &config, &decoder) == MA_SUCCESS)
|
||||||
(header[9] == 'A') && (header[10] == 'V') && (header[11] == 'E');
|
{
|
||||||
|
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)
|
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();
|
auto config = ma_decoder_config_init_default();
|
||||||
if (!info)
|
config.encodingFormat = ma_encoding_format_wav;
|
||||||
err() << "Failed to open WAV sound file (invalid or unsupported file)" << std::endl;
|
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)
|
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))
|
if (const ma_result result = ma_decoder_seek_to_pcm_frame(&*m_decoder, sampleOffset / m_channelCount);
|
||||||
err() << "Failed to seek WAV sound stream" << std::endl;
|
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)
|
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;
|
ma_uint64 framesRead{};
|
||||||
const auto startPos = static_cast<std::uint64_t>(m_stream->tell());
|
|
||||||
|
|
||||||
// Tracking of m_dataEnd is important to prevent sf::Music from reading
|
if (const ma_result result = ma_decoder_read_pcm_frames(&*m_decoder, samples, maxCount / m_channelCount, &framesRead);
|
||||||
// data until EOF, as WAV files may have metadata at the end.
|
result != MA_SUCCESS)
|
||||||
while ((count < maxCount) && (startPos + count * m_bytesPerSample < m_dataEnd))
|
err() << "Failed to read from wav sound stream: " << ma_result_description(result) << std::endl;
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 2:
|
return framesRead * m_channelCount;
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace sf::priv
|
} // namespace sf::priv
|
||||||
|
@ -29,6 +29,8 @@
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
#include <SFML/Audio/SoundFileReader.hpp>
|
#include <SFML/Audio/SoundFileReader.hpp>
|
||||||
|
|
||||||
|
#include <miniaudio.h>
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@ -58,6 +60,12 @@ public:
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
[[nodiscard]] static bool check(InputStream& stream);
|
[[nodiscard]] static bool check(InputStream& stream);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
/// \brief Destructor
|
||||||
|
///
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
~SoundFileReaderWav() override;
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
/// \brief Open a sound file for reading
|
/// \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;
|
[[nodiscard]] std::uint64_t read(std::int16_t* samples, std::uint64_t maxCount) override;
|
||||||
|
|
||||||
private:
|
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
|
// Member data
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
InputStream* m_stream{}; //!< Source stream to read from
|
std::optional<ma_decoder> m_decoder; //!< wav decoder
|
||||||
unsigned int m_bytesPerSample{}; //!< Size of a sample, in bytes
|
ma_uint32 m_channelCount{}; //!< Number of channels
|
||||||
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
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace sf::priv
|
} // namespace sf::priv
|
||||||
|
Loading…
Reference in New Issue
Block a user