mirror of
https://github.com/stenzek/duckstation.git
synced 2026-02-14 02:14:35 +00:00
WAVReader: Add in-memory parsing
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user