WAVReader: Add in-memory parsing

This commit is contained in:
Stenzek
2025-12-22 23:24:00 +10:00
parent b8e93855fe
commit c7c29d4141
2 changed files with 125 additions and 0 deletions

View File

@@ -267,6 +267,119 @@ std::optional<u32> WAVReader::ReadFrames(void* samples, u32 num_frames, Error* e
return read;
}
template<typename T>
static std::optional<std::span<const u8>> FindChunk(const std::span<const u8>& file, T* chunk, u32 tag, Error* error)
{
size_t offset = 0;
for (;;)
{
if (offset + sizeof(WAV_CHUNK_HEADER) > file.size())
{
Error::SetStringView(error, "Unexpected end of file while searching for chunk");
return {};
}
WAV_CHUNK_HEADER header;
std::memcpy(&header, file.data() + offset, sizeof(header));
if (header.chunk_id != tag)
{
offset += sizeof(header) + header.chunk_size;
continue;
}
if (header.chunk_size < (sizeof(T) - sizeof(header)))
{
Error::SetStringFmt(error, "Chunk is too small (required {} got {})", sizeof(T) - sizeof(header),
header.chunk_size);
return {};
}
else if (header.chunk_size > (file.size() - offset - sizeof(header)))
{
Error::SetStringView(error, "Chunk size exceeds file size");
return {};
}
if constexpr (sizeof(T) > sizeof(header))
{
if ((offset + sizeof(T)) > file.size())
{
Error::SetStringView(error, "Unexpected end of file while reading chunk header");
return {};
}
}
std::memcpy(chunk, file.data() + offset, sizeof(T));
return file.subspan(offset + sizeof(header), header.chunk_size);
}
}
std::optional<WAVReader::MemoryParseResult> WAVReader::ParseMemory(const void* data, size_t size,
Error* error /*= nullptr*/)
{
std::optional<WAVReader::MemoryParseResult> result;
WAV_HEADER file_header;
if (size < sizeof(file_header))
{
Error::SetStringView(error, "Data size is too small to be a valid WAV file");
return result;
}
std::memcpy(&file_header, data, sizeof(file_header));
if (file_header.chunk_id != RIFF_VALUE || file_header.format != WAVE_VALUE)
{
Error::SetStringView(error, "Invalid file header, must be RIFF");
return result;
}
const std::span<const u8> whole_file(reinterpret_cast<const u8*>(data) + sizeof(file_header),
size - sizeof(file_header));
WAV_FULL_HEADER::FormatChunk format;
if (!FindChunk(whole_file, &format, FMT_VALUE, error).has_value())
{
Error::AddPrefix(error, "Failed to get FMT chunk: ");
return result;
}
if (format.audio_format != 1) // PCM
{
Error::SetStringFmt(error, "Unsupported audio format {}", format.audio_format);
return result;
}
if (format.sample_rate == 0 || format.num_channels == 0 || format.bits_per_sample != 16)
{
Error::SetStringFmt(error, "Unsupported file format samplerate={} channels={} bits={}", format.sample_rate,
format.num_channels, format.bits_per_sample);
return result;
}
WAV_CHUNK_HEADER data_chunk;
std::optional<std::span<const u8>> sample_data = FindChunk(whole_file, &data_chunk, DATA_VALUE, error);
if (!sample_data.has_value())
{
Error::AddPrefix(error, "Failed to get DATA chunk: ");
return result;
}
const u32 num_frames = static_cast<u32>(sample_data->size() / (sizeof(s16) * format.num_channels));
if (num_frames == 0)
{
Error::SetStringFmt(error, "File has no frames");
return result;
}
result.emplace();
result->bytes_per_frame = sizeof(s16) * format.num_channels;
result->sample_rate = format.sample_rate;
result->num_channels = format.num_channels;
result->num_frames = num_frames;
result->sample_data = sample_data->data();
return result;
}
WAVWriter::WAVWriter() = default;
WAVWriter::WAVWriter(WAVWriter&& move)

View File

@@ -7,6 +7,7 @@
#include <cstdio>
#include <optional>
#include <vector>
class Error;
@@ -40,6 +41,17 @@ public:
std::optional<u32> ReadFrames(void* samples, u32 num_frames, Error* error = nullptr);
struct MemoryParseResult
{
u32 bytes_per_frame;
u32 sample_rate;
u32 num_channels;
u32 num_frames;
const void* sample_data;
};
static std::optional<MemoryParseResult> ParseMemory(const void* data, size_t size, Error* error = nullptr);
private:
using SampleType = s16;