Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
AudioWaveDecoder.cpp
Go to the documentation of this file.
2
3#include "Gsm610Codec.hpp"
4
5#include <cstdint>
6#include <limits>
7#include <optional>
8
9namespace monopoly::audio
10{
11 namespace
12 {
13 [[nodiscard]] std::uint16_t readU16(
14 std::span<const std::byte> bytes,
15 std::size_t offset) noexcept
16 {
17 return
18 static_cast<std::uint16_t>(
19 std::to_integer<std::uint8_t>(bytes[offset])) |
20 static_cast<std::uint16_t>(
21 std::to_integer<std::uint8_t>(bytes[offset + 1]) << 8U);
22 }
23
24
25 [[nodiscard]] std::uint32_t readU32(
26 std::span<const std::byte> bytes,
27 std::size_t offset) noexcept
28 {
29 return
30 static_cast<std::uint32_t>(
31 std::to_integer<std::uint8_t>(bytes[offset])) |
32 (static_cast<std::uint32_t>(
33 std::to_integer<std::uint8_t>(bytes[offset + 1])) << 8U) |
34 (static_cast<std::uint32_t>(
35 std::to_integer<std::uint8_t>(bytes[offset + 2])) << 16U) |
36 (static_cast<std::uint32_t>(
37 std::to_integer<std::uint8_t>(bytes[offset + 3])) << 24U);
38 }
39
40
41 [[nodiscard]] bool fourCC(
42 std::span<const std::byte> bytes,
43 std::size_t offset,
44 char a, char b, char c, char d) noexcept
45 {
46 return bytes[offset] == static_cast<std::byte>(a) &&
47 bytes[offset + 1] == static_cast<std::byte>(b) &&
48 bytes[offset + 2] == static_cast<std::byte>(c) &&
49 bytes[offset + 3] == static_cast<std::byte>(d);
50 }
51
52
53 struct WaveChunks
54 {
55 std::size_t formatOffset{};
56 std::uint32_t formatSize{};
57 std::size_t dataOffset{};
58 std::uint32_t dataSize{};
59 };
60
61
62 [[nodiscard]] std::optional<WaveChunks>
63 findWaveChunks(std::span<const std::byte> bytes) noexcept
64 {
65 if (bytes.size() < 12 ||
66 !fourCC(bytes, 0, 'R', 'I', 'F', 'F') ||
67 !fourCC(bytes, 8, 'W', 'A', 'V', 'E'))
68 return std::nullopt;
69
70 const std::uint64_t end64 =
71 8ULL + static_cast<std::uint64_t>(readU32(bytes, 4));
72 if (end64 > bytes.size() || end64 < 12)
73 return std::nullopt;
74 const auto end = static_cast<std::size_t>(end64);
75
76 WaveChunks result;
77 std::size_t cursor = 12;
78 while (cursor + 8 <= end)
79 {
80 const auto size = readU32(bytes, cursor + 4);
81 const auto payload = cursor + 8;
82 const std::uint64_t payloadEnd64 =
83 static_cast<std::uint64_t>(payload) + size;
84 if (payloadEnd64 > end)
85 return std::nullopt;
86
87 if (fourCC(bytes, cursor, 'f', 'm', 't', ' '))
88 {
89 result.formatOffset = payload;
90 result.formatSize = size;
91 }
92 else if (fourCC(bytes, cursor, 'd', 'a', 't', 'a'))
93 {
94 result.dataOffset = payload;
95 result.dataSize = size;
96 }
97
98 const auto next64 = payloadEnd64 + (size & 1U);
99 if (next64 > end)
100 {
101 // Retail dialog WAV49 files omit the final data padding
102 // byte. Their complete encoded payload is still bounded
103 // by RIFF; L_Sound accepts this terminal chunk as well.
104 // Do not tolerate missing samples or interior padding.
105 if (payloadEnd64 != end ||
106 !fourCC(bytes, cursor, 'd', 'a', 't', 'a'))
107 return std::nullopt;
108 cursor = end;
109 continue;
110 }
111 cursor = static_cast<std::size_t>(next64);
112 }
113
114 if (cursor != end || result.formatSize == 0 || result.dataSize == 0)
115 return std::nullopt;
116 return result;
117 }
118
119
120 [[nodiscard]] std::expected<DecodedWave, std::string>
122 std::span<const std::byte> bytes,
123 const WaveChunks& chunks)
124 {
125 if (chunks.formatSize < 20)
126 return std::unexpected(
127 "GSM 6.10 WAVE fmt chunk is shorter than WAVEFORMATEX");
128
129 const auto formatTag = readU16(bytes, chunks.formatOffset);
130 if (formatTag != voicechat::gsm610::FormatTag)
131 return std::unexpected("WAVE is not Microsoft GSM 6.10");
132
133 const auto channels = readU16(bytes, chunks.formatOffset + 2);
134 const auto sampleRate = readU32(bytes, chunks.formatOffset + 4);
135 const auto blockAlign = readU16(bytes, chunks.formatOffset + 12);
136 const auto bitsPerSample = readU16(bytes, chunks.formatOffset + 14);
137 const auto extraSize = readU16(bytes, chunks.formatOffset + 16);
138 const auto samplesPerBlock = readU16(bytes, chunks.formatOffset + 18);
139
140 if (channels != 1 ||
141 sampleRate == 0 ||
142 sampleRate > static_cast<std::uint32_t>(
143 std::numeric_limits<int>::max()) ||
144 blockAlign != voicechat::gsm610::BlockAlign ||
145 bitsPerSample != 0 ||
146 extraSize < 2 ||
148 {
149 return std::unexpected(
150 "unsupported Microsoft GSM 6.10 WAVE format");
151 }
152 if (chunks.dataSize % voicechat::gsm610::BlockAlign != 0)
153 return std::unexpected(
154 "GSM 6.10 WAVE data is not block aligned");
155
156 const auto* encodedData =
157 reinterpret_cast<const std::uint8_t*>(
158 bytes.data() + chunks.dataOffset);
159 const std::span<const std::uint8_t> encoded{
161 chunks.dataSize};
162
163 voicechat::gsm610::Decoder decoder;
164 auto pcm = decoder.decodeToU8(encoded);
165 if (!pcm)
166 return std::unexpected(pcm.error());
167
168 DecodedWave result;
169 result.spec = SDL_AudioSpec{
171 1,
172 static_cast<int>(sampleRate)};
173 result.pcm.assign(pcm->begin(), pcm->end());
174 return result;
175 }
176
177
178 [[nodiscard]] std::expected<DecodedWave, std::string>
179 decodeWithSDL(std::span<const std::byte> bytes)
180 {
182 bytes.data(), bytes.size());
183 if (!io)
184 return std::unexpected(
185 std::string("SDL_IOFromConstMem: ") + SDL_GetError());
186
187 SDL_AudioSpec spec{};
188 Uint8* decoded{};
190 if (!SDL_LoadWAV_IO(
191 io, true, &spec, &decoded, &decodedLength))
192 return std::unexpected(
193 std::string("SDL_LoadWAV_IO: ") + SDL_GetError());
194
195 DecodedWave result;
196 result.spec = spec;
197 result.pcm.assign(decoded, decoded + decodedLength);
199 if (result.pcm.empty())
200 return std::unexpected(
201 "decoded WAVE contains no PCM samples");
202 return result;
203 }
204 }
205
206
207 std::expected<DecodedWave, std::string>
208 decodeWave(std::span<const std::byte> bytes)
209 {
210 const auto chunks = findWaveChunks(bytes);
211 if (chunks &&
212 chunks->formatSize >= 2 &&
213 readU16(bytes, chunks->formatOffset) ==
215 return decodeGsm610(bytes, *chunks);
216
217 return decodeWithSDL(bytes);
218 }
219}
std::size_t offset
std::uint32_t formatSize
std::size_t formatOffset
std::size_t dataOffset
std::uint32_t dataSize
std::vector< std::byte > bytes
unsigned index
std::expected< DecodedWave, std::string > decodeWave(std::span< const std::byte > bytes)
std::size_t size()
constexpr std::uint16_t SamplesPerBlock
constexpr std::uint16_t FormatTag
constexpr std::uint16_t BlockAlign
constexpr std::uint32_t fourCC(char a, char b, char c, char d) noexcept