/src/serenity/Userland/Libraries/LibAudio/WavLoader.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> |
3 | | * Copyright (c) 2021-2023, kleines Filmröllchen <filmroellchen@serenityos.org> |
4 | | * |
5 | | * SPDX-License-Identifier: BSD-2-Clause |
6 | | */ |
7 | | |
8 | | #include "WavLoader.h" |
9 | | #include "LoaderError.h" |
10 | | #include "WavTypes.h" |
11 | | #include <AK/Debug.h> |
12 | | #include <AK/Endian.h> |
13 | | #include <AK/FixedArray.h> |
14 | | #include <AK/MemoryStream.h> |
15 | | #include <AK/NonnullOwnPtr.h> |
16 | | #include <AK/NumericLimits.h> |
17 | | #include <AK/Try.h> |
18 | | |
19 | | namespace Audio { |
20 | | |
21 | | WavLoaderPlugin::WavLoaderPlugin(NonnullOwnPtr<SeekableStream> stream) |
22 | 0 | : LoaderPlugin(move(stream)) |
23 | 0 | { |
24 | 0 | } |
25 | | |
26 | | bool WavLoaderPlugin::sniff(SeekableStream& stream) |
27 | 0 | { |
28 | 0 | auto riff = stream.read_value<RIFF::ChunkID>(); |
29 | 0 | if (riff.is_error()) |
30 | 0 | return false; |
31 | 0 | if (riff.value() != RIFF::riff_magic) |
32 | 0 | return false; |
33 | | |
34 | 0 | auto size = stream.read_value<LittleEndian<u32>>(); |
35 | 0 | if (size.is_error()) |
36 | 0 | return false; |
37 | | |
38 | 0 | auto wave = stream.read_value<RIFF::ChunkID>(); |
39 | 0 | return !wave.is_error() && wave.value() == Wav::wave_subformat_id; |
40 | 0 | } |
41 | | |
42 | | ErrorOr<NonnullOwnPtr<LoaderPlugin>, LoaderError> WavLoaderPlugin::create(NonnullOwnPtr<SeekableStream> stream) |
43 | 0 | { |
44 | 0 | auto loader = make<WavLoaderPlugin>(move(stream)); |
45 | 0 | TRY(loader->parse_header()); |
46 | 0 | return loader; |
47 | 0 | } |
48 | | |
49 | | template<typename SampleReader> |
50 | | MaybeLoaderError WavLoaderPlugin::read_samples_from_stream(Stream& stream, SampleReader read_sample, FixedArray<Sample>& samples) const |
51 | 0 | { |
52 | 0 | switch (m_num_channels) { |
53 | 0 | case 1: |
54 | 0 | for (auto& sample : samples) |
55 | 0 | sample = Sample(TRY(read_sample(stream))); |
56 | 0 | break; |
57 | 0 | case 2: |
58 | 0 | for (auto& sample : samples) { |
59 | 0 | auto left_channel_sample = TRY(read_sample(stream)); |
60 | 0 | auto right_channel_sample = TRY(read_sample(stream)); |
61 | 0 | sample = Sample(left_channel_sample, right_channel_sample); |
62 | 0 | } |
63 | 0 | break; |
64 | 0 | default: |
65 | 0 | VERIFY_NOT_REACHED(); |
66 | 0 | } |
67 | 0 | return {}; |
68 | 0 | } |
69 | | |
70 | | // There's no i24 type + we need to do the endianness conversion manually anyways. |
71 | | static ErrorOr<double> read_sample_int24(Stream& stream) |
72 | 0 | { |
73 | 0 | i32 sample1 = TRY(stream.read_value<u8>()); |
74 | 0 | i32 sample2 = TRY(stream.read_value<u8>()); |
75 | 0 | i32 sample3 = TRY(stream.read_value<u8>()); |
76 | |
|
77 | 0 | i32 value = 0; |
78 | 0 | value = sample1; |
79 | 0 | value |= sample2 << 8; |
80 | 0 | value |= sample3 << 16; |
81 | | // Sign extend the value, as it can currently not have the correct sign. |
82 | 0 | value = (value << 8) >> 8; |
83 | | // Range of value is now -2^23 to 2^23-1 and we can rescale normally. |
84 | 0 | return static_cast<double>(value) / static_cast<double>((1 << 23) - 1); |
85 | 0 | } |
86 | | |
87 | | template<typename T> |
88 | | static ErrorOr<double> read_sample(Stream& stream) |
89 | 0 | { |
90 | 0 | T sample { 0 }; |
91 | 0 | TRY(stream.read_until_filled(Bytes { &sample, sizeof(T) })); |
92 | | // Remap integer samples to normalized floating-point range of -1 to 1. |
93 | 0 | if constexpr (IsIntegral<T>) { |
94 | 0 | if constexpr (NumericLimits<T>::is_signed()) { |
95 | | // Signed integer samples are centered around zero, so this division is enough. |
96 | 0 | return static_cast<double>(AK::convert_between_host_and_little_endian(sample)) / static_cast<double>(NumericLimits<T>::max()); |
97 | 0 | } else { |
98 | | // Unsigned integer samples, on the other hand, need to be shifted to center them around zero. |
99 | | // The first division therefore remaps to the range 0 to 2. |
100 | 0 | return static_cast<double>(AK::convert_between_host_and_little_endian(sample)) / (static_cast<double>(NumericLimits<T>::max()) / 2.0) - 1.0; |
101 | 0 | } |
102 | 0 | } else { |
103 | 0 | return static_cast<double>(AK::convert_between_host_and_little_endian(sample)); |
104 | 0 | } |
105 | 0 | } Unexecuted instantiation: WavLoader.cpp:AK::ErrorOr<double, AK::Error> Audio::read_sample<unsigned char>(AK::Stream&) Unexecuted instantiation: WavLoader.cpp:AK::ErrorOr<double, AK::Error> Audio::read_sample<short>(AK::Stream&) Unexecuted instantiation: WavLoader.cpp:AK::ErrorOr<double, AK::Error> Audio::read_sample<float>(AK::Stream&) Unexecuted instantiation: WavLoader.cpp:AK::ErrorOr<double, AK::Error> Audio::read_sample<double>(AK::Stream&) |
106 | | |
107 | | LoaderSamples WavLoaderPlugin::samples_from_pcm_data(ReadonlyBytes data, size_t samples_to_read) const |
108 | 0 | { |
109 | 0 | FixedArray<Sample> samples = TRY(FixedArray<Sample>::create(samples_to_read)); |
110 | 0 | FixedMemoryStream stream { data }; |
111 | |
|
112 | 0 | switch (m_sample_format) { |
113 | 0 | case PcmSampleFormat::Uint8: |
114 | 0 | TRY(read_samples_from_stream(stream, read_sample<u8>, samples)); |
115 | 0 | break; |
116 | 0 | case PcmSampleFormat::Int16: |
117 | 0 | TRY(read_samples_from_stream(stream, read_sample<i16>, samples)); |
118 | 0 | break; |
119 | 0 | case PcmSampleFormat::Int24: |
120 | 0 | TRY(read_samples_from_stream(stream, read_sample_int24, samples)); |
121 | 0 | break; |
122 | 0 | case PcmSampleFormat::Float32: |
123 | 0 | TRY(read_samples_from_stream(stream, read_sample<float>, samples)); |
124 | 0 | break; |
125 | 0 | case PcmSampleFormat::Float64: |
126 | 0 | TRY(read_samples_from_stream(stream, read_sample<double>, samples)); |
127 | 0 | break; |
128 | 0 | default: |
129 | 0 | VERIFY_NOT_REACHED(); |
130 | 0 | } |
131 | | |
132 | 0 | return samples; |
133 | 0 | } |
134 | | |
135 | | ErrorOr<Vector<FixedArray<Sample>>, LoaderError> WavLoaderPlugin::load_chunks(size_t samples_to_read_from_input) |
136 | 0 | { |
137 | 0 | auto remaining_samples = m_total_samples - m_loaded_samples; |
138 | 0 | if (remaining_samples <= 0) |
139 | 0 | return Vector<FixedArray<Sample>> {}; |
140 | | |
141 | | // One "sample" contains data from all channels. |
142 | | // In the Wave spec, this is also called a block. |
143 | 0 | size_t bytes_per_sample |
144 | 0 | = m_num_channels * pcm_bits_per_sample(m_sample_format) / 8; |
145 | |
|
146 | 0 | auto samples_to_read = min(samples_to_read_from_input, remaining_samples); |
147 | 0 | auto bytes_to_read = samples_to_read * bytes_per_sample; |
148 | |
|
149 | 0 | dbgln_if(AWAVLOADER_DEBUG, "Read {} bytes WAV with num_channels {} sample rate {}, " |
150 | 0 | "bits per sample {}, sample format {}", |
151 | 0 | bytes_to_read, m_num_channels, m_sample_rate, |
152 | 0 | pcm_bits_per_sample(m_sample_format), sample_format_name(m_sample_format)); |
153 | |
|
154 | 0 | auto sample_data = TRY(ByteBuffer::create_zeroed(bytes_to_read)); |
155 | 0 | TRY(m_stream->read_until_filled(sample_data.bytes())); |
156 | | |
157 | | // m_loaded_samples should contain the amount of actually loaded samples |
158 | 0 | m_loaded_samples += samples_to_read; |
159 | 0 | Vector<FixedArray<Sample>> samples; |
160 | 0 | TRY(samples.try_append(TRY(samples_from_pcm_data(sample_data.bytes(), samples_to_read)))); |
161 | 0 | return samples; |
162 | 0 | } |
163 | | |
164 | | MaybeLoaderError WavLoaderPlugin::seek(int sample_index) |
165 | 0 | { |
166 | 0 | dbgln_if(AWAVLOADER_DEBUG, "seek sample_index {}", sample_index); |
167 | 0 | if (sample_index < 0 || sample_index >= static_cast<int>(m_total_samples)) |
168 | 0 | return LoaderError { LoaderError::Category::Internal, m_loaded_samples, "Seek outside the sample range"_fly_string }; |
169 | | |
170 | 0 | size_t sample_offset = m_byte_offset_of_data_samples + static_cast<size_t>(sample_index * m_num_channels * (pcm_bits_per_sample(m_sample_format) / 8)); |
171 | |
|
172 | 0 | TRY(m_stream->seek(sample_offset, SeekMode::SetPosition)); |
173 | |
|
174 | 0 | m_loaded_samples = sample_index; |
175 | 0 | return {}; |
176 | 0 | } |
177 | | |
178 | | // Specification reference: http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html |
179 | | MaybeLoaderError WavLoaderPlugin::parse_header() |
180 | 0 | { |
181 | 0 | #define CHECK(check, category, msg) \ |
182 | 0 | do { \ |
183 | 0 | if (!(check)) { \ |
184 | 0 | return LoaderError { category, static_cast<size_t>(TRY(m_stream->tell())), TRY(String::formatted("WAV header: {}", msg)) }; \ |
185 | 0 | } \ |
186 | 0 | } while (0) |
187 | |
|
188 | 0 | auto file_header = TRY(m_stream->read_value<RIFF::FileHeader>()); |
189 | 0 | CHECK(file_header.magic() == RIFF::riff_magic, LoaderError::Category::Format, "RIFF header magic invalid"); |
190 | 0 | CHECK(file_header.subformat == Wav::wave_subformat_id, LoaderError::Category::Format, "WAVE subformat id invalid"); |
191 | | |
192 | 0 | auto format_chunk = TRY(m_stream->read_value<RIFF::OwnedChunk>()); |
193 | 0 | CHECK(format_chunk.id().as_ascii_string() == Wav::format_chunk_id, LoaderError::Category::Format, "FMT chunk id invalid"); |
194 | | |
195 | 0 | auto format_stream = format_chunk.data_stream(); |
196 | 0 | u16 audio_format = TRY(format_stream.read_value<LittleEndian<u16>>()); |
197 | 0 | CHECK(audio_format == to_underlying(Wav::WaveFormat::Pcm) || audio_format == to_underlying(Wav::WaveFormat::IEEEFloat) || audio_format == to_underlying(Wav::WaveFormat::Extensible), |
198 | 0 | LoaderError::Category::Unimplemented, "Audio format not supported"); |
199 | | |
200 | 0 | m_num_channels = TRY(format_stream.read_value<LittleEndian<u16>>()); |
201 | 0 | CHECK(m_num_channels == 1 || m_num_channels == 2, LoaderError::Category::Unimplemented, "Channel count"); |
202 | | |
203 | 0 | m_sample_rate = TRY(format_stream.read_value<LittleEndian<u32>>()); |
204 | | // Data rate; can be ignored. |
205 | 0 | TRY(format_stream.read_value<LittleEndian<u32>>()); |
206 | 0 | u16 block_size_bytes = TRY(format_stream.read_value<LittleEndian<u16>>()); |
207 | |
|
208 | 0 | u16 bits_per_sample = TRY(format_stream.read_value<LittleEndian<u16>>()); |
209 | |
|
210 | 0 | if (audio_format == to_underlying(Wav::WaveFormat::Extensible)) { |
211 | 0 | CHECK(format_chunk.size() == 40, LoaderError::Category::Format, "Extensible fmt size is not 40 bytes"); |
212 | | |
213 | | // Discard everything until the GUID. |
214 | | // We've already read 16 bytes from the stream. The GUID starts in another 8 bytes. |
215 | 0 | TRY(format_stream.read_value<LittleEndian<u64>>()); |
216 | | |
217 | | // Get the underlying audio format from the first two bytes of GUID |
218 | 0 | u16 guid_subformat = TRY(format_stream.read_value<LittleEndian<u16>>()); |
219 | 0 | CHECK(guid_subformat == to_underlying(Wav::WaveFormat::Pcm) || guid_subformat == to_underlying(Wav::WaveFormat::IEEEFloat), LoaderError::Category::Unimplemented, "GUID SubFormat not supported"); |
220 | | |
221 | 0 | audio_format = guid_subformat; |
222 | 0 | } |
223 | | |
224 | 0 | if (audio_format == to_underlying(Wav::WaveFormat::Pcm)) { |
225 | 0 | CHECK(bits_per_sample == 8 || bits_per_sample == 16 || bits_per_sample == 24, LoaderError::Category::Unimplemented, "PCM bits per sample not supported"); |
226 | | |
227 | | // We only support 8-24 bit audio right now because other formats are uncommon |
228 | 0 | if (bits_per_sample == 8) { |
229 | 0 | m_sample_format = PcmSampleFormat::Uint8; |
230 | 0 | } else if (bits_per_sample == 16) { |
231 | 0 | m_sample_format = PcmSampleFormat::Int16; |
232 | 0 | } else if (bits_per_sample == 24) { |
233 | 0 | m_sample_format = PcmSampleFormat::Int24; |
234 | 0 | } |
235 | 0 | } else if (audio_format == to_underlying(Wav::WaveFormat::IEEEFloat)) { |
236 | 0 | CHECK(bits_per_sample == 32 || bits_per_sample == 64, LoaderError::Category::Unimplemented, "Float bits per sample not supported"); |
237 | | |
238 | | // Again, only the common 32 and 64 bit |
239 | 0 | if (bits_per_sample == 32) { |
240 | 0 | m_sample_format = PcmSampleFormat::Float32; |
241 | 0 | } else if (bits_per_sample == 64) { |
242 | 0 | m_sample_format = PcmSampleFormat::Float64; |
243 | 0 | } |
244 | 0 | } |
245 | | |
246 | 0 | CHECK(block_size_bytes == (m_num_channels * (bits_per_sample / 8)), LoaderError::Category::Format, "Block size invalid"); |
247 | | |
248 | 0 | dbgln_if(AWAVLOADER_DEBUG, "WAV format {} at {} bit, {} channels, rate {}Hz ", |
249 | 0 | sample_format_name(m_sample_format), pcm_bits_per_sample(m_sample_format), m_num_channels, m_sample_rate); |
250 | | |
251 | | // Read all chunks before DATA. |
252 | 0 | bool found_data = false; |
253 | 0 | while (!found_data) { |
254 | 0 | auto chunk_header = TRY(m_stream->read_value<RIFF::ChunkID>()); |
255 | 0 | if (chunk_header == Wav::data_chunk_id) { |
256 | 0 | found_data = true; |
257 | 0 | } else { |
258 | 0 | TRY(m_stream->seek(-RIFF::chunk_id_size, SeekMode::FromCurrentPosition)); |
259 | 0 | auto chunk = TRY(m_stream->read_value<RIFF::OwnedChunk>()); |
260 | 0 | if (chunk.id() == RIFF::list_chunk_id) { |
261 | 0 | auto maybe_list = chunk.data_stream().read_value<RIFF::OwnedList>(); |
262 | 0 | if (maybe_list.is_error()) { |
263 | 0 | dbgln("WAV Warning: LIST chunk invalid, error: {}", maybe_list.release_error()); |
264 | 0 | continue; |
265 | 0 | } |
266 | | |
267 | 0 | auto list = maybe_list.release_value(); |
268 | 0 | if (list.type == Wav::info_chunk_id) { |
269 | 0 | auto maybe_error = load_wav_info_block(move(list.chunks)); |
270 | 0 | if (maybe_error.is_error()) |
271 | 0 | dbgln("WAV Warning: INFO chunk invalid, error: {}", maybe_error.release_error()); |
272 | |
|
273 | 0 | } else { |
274 | 0 | dbgln("Unhandled WAV list of type {} with {} subchunks", list.type.as_ascii_string(), list.chunks.size()); |
275 | 0 | } |
276 | 0 | } else { |
277 | 0 | dbgln_if(AWAVLOADER_DEBUG, "Unhandled WAV chunk of type {}, size {} bytes", chunk.id().as_ascii_string(), chunk.size()); |
278 | 0 | } |
279 | 0 | } |
280 | 0 | } |
281 | | |
282 | 0 | u32 data_size = TRY(m_stream->read_value<LittleEndian<u32>>()); |
283 | 0 | CHECK(found_data, LoaderError::Category::Format, "Found no data chunk"); |
284 | | |
285 | 0 | m_total_samples = data_size / block_size_bytes; |
286 | |
|
287 | 0 | dbgln_if(AWAVLOADER_DEBUG, "WAV data size {}, bytes per sample {}, total samples {}", |
288 | 0 | data_size, |
289 | 0 | block_size_bytes, |
290 | 0 | m_total_samples); |
291 | |
|
292 | 0 | m_byte_offset_of_data_samples = TRY(m_stream->tell()); |
293 | 0 | return {}; |
294 | 0 | } |
295 | | |
296 | | // http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf page 23 (LIST type) |
297 | | // We only recognize the relevant official metadata types; types added in later errata of RIFF are not relevant for audio. |
298 | | MaybeLoaderError WavLoaderPlugin::load_wav_info_block(Vector<RIFF::OwnedChunk> info_chunks) |
299 | 0 | { |
300 | 0 | for (auto const& chunk : info_chunks) { |
301 | 0 | auto chunk_id = chunk.id(); |
302 | 0 | auto metadata_name = chunk_id.as_ascii_string(); |
303 | | // Chunk contents are zero-terminated strings "ZSTR", so we just drop the null terminator. |
304 | 0 | StringView metadata_text { chunk.data().trim(chunk.size() - 1) }; |
305 | | // Note that we assume chunks to be unique, since that seems to almost always be the case. |
306 | | // Worst case we just drop some metadata. |
307 | 0 | if (metadata_name == "IART"sv) { |
308 | | // Artists are combined together with semicolons, at least when you edit them in Windows File Explorer. |
309 | 0 | auto artists = metadata_text.split_view(";"sv); |
310 | 0 | for (auto artist : artists) |
311 | 0 | TRY(m_metadata.add_person(Person::Role::Artist, TRY(String::from_utf8(artist)))); |
312 | 0 | } else if (metadata_name == "ICMT"sv) { |
313 | 0 | m_metadata.comment = TRY(String::from_utf8(metadata_text)); |
314 | 0 | } else if (metadata_name == "ICOP"sv) { |
315 | 0 | m_metadata.copyright = TRY(String::from_utf8(metadata_text)); |
316 | 0 | } else if (metadata_name == "ICRD"sv) { |
317 | 0 | m_metadata.unparsed_time = TRY(String::from_utf8(metadata_text)); |
318 | 0 | } else if (metadata_name == "IENG"sv) { |
319 | 0 | TRY(m_metadata.add_person(Person::Role::Engineer, TRY(String::from_utf8(metadata_text)))); |
320 | 0 | } else if (metadata_name == "IGNR"sv) { |
321 | 0 | m_metadata.genre = TRY(String::from_utf8(metadata_text)); |
322 | 0 | } else if (metadata_name == "INAM"sv) { |
323 | 0 | m_metadata.title = TRY(String::from_utf8(metadata_text)); |
324 | 0 | } else if (metadata_name == "IPRD"sv) { |
325 | 0 | m_metadata.album = TRY(String::from_utf8(metadata_text)); |
326 | 0 | } else if (metadata_name == "ISFT"sv) { |
327 | 0 | m_metadata.encoder = TRY(String::from_utf8(metadata_text)); |
328 | 0 | } else if (metadata_name == "ISRC"sv) { |
329 | 0 | TRY(m_metadata.add_person(Person::Role::Publisher, TRY(String::from_utf8(metadata_text)))); |
330 | 0 | } else { |
331 | 0 | TRY(m_metadata.add_miscellaneous(TRY(String::from_utf8(metadata_name)), TRY(String::from_utf8(metadata_text)))); |
332 | 0 | } |
333 | 0 | } |
334 | 0 | return {}; |
335 | 0 | } |
336 | | |
337 | | } |