Audacity 3.2.0
WavPackCompressor.cpp
Go to the documentation of this file.
1/* SPDX-License-Identifier: GPL-2.0-or-later */
2/*!********************************************************************
3
4 Audacity: A Digital Audio Editor
5
6 WavPackCompressor.cpp
7
8 Dmitry Vedenko
9
10**********************************************************************/
11
12#include "WavPackCompressor.h"
13
14#include <cmath>
15#include <cstdint>
16#include <cstring>
17
18#include <wavpack/wavpack.h>
19
20#include "SampleFormat.h"
21#include "SampleBlock.h"
22
23#include "MemoryX.h"
24
25#include "FromChars.h"
26
28{
29namespace
30{
31struct Exporter final
32{
33 WavpackContext* Context { nullptr };
35 std::vector<uint8_t> CompressedData;
36
38 : Block { std::move(block) }
39 {
40 WavpackConfig config = {};
41
42 config.num_channels = 1;
43 config.channel_mask = 0x4;
44 // Sample rate is irrelevant, so just set it to something
45 config.sample_rate = 48000;
46
47 config.bytes_per_sample = SAMPLE_SIZE_DISK(Block.Format);
48 config.bits_per_sample = config.bytes_per_sample * 8;
49 config.float_norm_exp = Block.Format == floatSample ? 127 : 0;
50
51 config.flags = CONFIG_FAST_FLAG;
52
53 Context = WavpackOpenFileOutput(WriteBlock, this, nullptr);
54
55 if (
56 !WavpackSetConfiguration(
57 Context, &config, Block.Block->GetSampleCount()) ||
58 !WavpackPackInit(Context))
59 {
60 WavpackCloseFile(Context);
61 Context = nullptr;
62 }
63 }
64
66 {
67 if (Context != nullptr)
68 WavpackCloseFile(Context);
69 }
70
71 std::vector<uint8_t> Compress()
72 {
73 const auto sampleFormat = Block.Format;
74 const auto sampleCount = Block.Block->GetSampleCount();
75 const auto dataSize = sampleCount * SAMPLE_SIZE(sampleFormat);
76
77 std::vector<std::remove_pointer_t<samplePtr>> sampleData;
78 sampleData.resize(dataSize);
79
80 const size_t samplesRead = Block.Block->GetSamples(
81 sampleData.data(), Block.Format, 0, sampleCount, false);
82
83 // Reserve 1.5 times the size of the original data
84 // The compressed data will be smaller than the original data,
85 // but we overallocate just in case
86 CompressedData.reserve(sampleData.size() * 3 / 2);
87
89 {
90 constexpr size_t conversionSamplesCount = 4096;
91
92 const int16_t* int16Data = reinterpret_cast<const int16_t*>(sampleData.data());
93 std::vector<int32_t> buffer;
94 buffer.resize(conversionSamplesCount);
95
96 for (size_t firstSample = 0; firstSample < samplesRead;
97 firstSample += conversionSamplesCount)
98 {
99 const auto samplesThisRun =
100 std::min(conversionSamplesCount, samplesRead - firstSample);
101
102 for (size_t i = 0; i < samplesThisRun; ++i)
103 buffer[i] =
104 (static_cast<int32_t>(int16Data[firstSample + i]) * 65536) >>
105 16;
106
107 WavpackPackSamples(Context, buffer.data(), samplesThisRun);
108 }
109 }
110 else
111 {
112 const void* data = sampleData.data();
113 WavpackPackSamples(
114 Context, static_cast<int32_t*>(const_cast<void*>(data)),
115 samplesRead);
116 }
117
118 Flush();
119
120 return std::move(CompressedData);
121 }
122
123 void Feed(int32_t* data, int32_t count)
124 {
125 if (Context == nullptr)
126 return;
127
128 WavpackPackSamples(Context, data, count);
129 }
130
131 void Flush()
132 {
133 if (Context == nullptr)
134 return;
135
136 WavpackFlushSamples(Context);
137
138 const std::string formatString = std::to_string(unsigned(Block.Format));
139 WavpackAppendTagItem(
140 Context, "FORMAT", formatString.data(), formatString.size());
141
142 const std::string blockIdString = std::to_string(Block.Id);
143 WavpackAppendTagItem(
144 Context, "BLOCK_ID", blockIdString.data(), blockIdString.size());
145
146 WavpackAppendTagItem(
147 Context, "HASH", Block.Hash.data(), Block.Hash.size());
148
149 WavpackWriteTag(Context);
150 WavpackCloseFile(Context);
151
152 Context = nullptr;
153 }
154
155 static int WriteBlock(void* id, void* data, int32_t length)
156 {
157 if (id == nullptr || data == nullptr || length == 0)
158 return true;
159
160 Exporter* exporter = static_cast<Exporter*>(id);
161
162 auto start = reinterpret_cast<uint8_t*>(data);
163 auto end = start + length;
164
165 exporter->CompressedData.insert(
166 exporter->CompressedData.end(), start, end);
167
168 return true;
169 }
170}; // struct Exporter
171
172struct Importer final
173{
174 WavpackContext* Context { nullptr };
175
176 // Stream data
177 const void* Data { nullptr };
178 const int64_t Size { Offset };
179
180 int64_t Offset { 0 };
181
182 int Mode {};
183 uint32_t SamplesCount {};
184
185 uint8_t UngetcChar { 0 };
186 bool UngetcFlag { false };
187
188 // Header data
189
191 int64_t BlockId { -1 };
192
193 // Out buffers
194 std::vector<int32_t> Int32Buffer;
195 std::vector<int16_t> Int16Buffer;
196 std::vector<float> FloatBuffer;
197
198 bool IsValid() const
199 {
200 return Context != nullptr && SamplesCount > 0 &&
201 Format != undefinedSample && BlockId >= 0;
202 }
203
204 Importer(const void* data, const int64_t size)
205 : Data { data }
206 , Size { size }
207 {
208 if (Data == nullptr || Size == 0)
209 return;
210
211 char error[81];
212
213 Context = WavpackOpenFileInputEx64(
214 &raw_reader, this, nullptr, error, OPEN_DSD_AS_PCM | OPEN_TAGS, 0);
215
216 if (Context == nullptr)
217 return;
218
219 Mode = WavpackGetMode(Context);
220 SamplesCount = WavpackGetNumSamples(Context);
221
222 const auto formatString = ReadTag("FORMAT");
223 unsigned format {};
224 auto result = FromChars(
225 formatString.c_str(), formatString.c_str() + formatString.size(),
226 format);
227 if (result.ec == std::errc {})
228 Format = static_cast<sampleFormat>(format);
229
230 auto blockIdString = ReadTag("BLOCK_ID");
231 FromChars(
232 blockIdString.c_str(), blockIdString.c_str() + blockIdString.size(),
233 BlockId);
234 }
235
236 std::string ReadTag(const char* tagName)
237 {
238 if (Context == nullptr)
239 return {};
240
241 const auto tagLength = WavpackGetTagItem(Context, tagName, nullptr, 0);
242
243 std::string tag;
244
245 if (tagLength > 0)
246 {
247 tag.resize(tagLength + 1);
248 WavpackGetTagItem(Context, tagName, tag.data(), tagLength + 1);
249 tag.resize(tagLength);
250 }
251
252 return tag;
253 }
254
256 {
257 if (Context != nullptr)
258 WavpackCloseFile(Context);
259 }
260
261 bool Unpack()
262 {
263 if (!IsValid())
264 return false;
265
266 Int32Buffer.resize(SamplesCount);
267
268 if (Format == int16Sample)
269 Int16Buffer.resize(SamplesCount);
270 else if (Format == floatSample)
271 FloatBuffer.resize(SamplesCount);
272
273 const auto samplesRead =
274 WavpackUnpackSamples(Context, Int32Buffer.data(), SamplesCount);
275
276 if (samplesRead != SamplesCount)
277 return false;
278
279 const bool floatSamples = (Mode & MODE_FLOAT) == MODE_FLOAT;
280
281 if (floatSamples)
282 {
283 FloatBuffer.resize(SamplesCount);
284 std::memcpy(
285 FloatBuffer.data(), Int32Buffer.data(),
286 SamplesCount * sizeof(float));
287 }
288 else if (Format == int16Sample)
289 {
290 const auto bytesPerSample = WavpackGetBytesPerSample(Context);
291
292 if (bytesPerSample == 1)
293 {
294 for (size_t i = 0; i < SamplesCount; ++i)
295 Int16Buffer[i] = static_cast<int16_t>(Int32Buffer[i]) << 8;
296 }
297 else if (bytesPerSample == 2)
298 {
299 for (size_t i = 0; i < SamplesCount; ++i)
300 Int16Buffer[i] = static_cast<int16_t>(Int32Buffer[i]);
301 }
302 else
303 {
304 assert(false);
305 return false;
306 }
307 }
308
309 return true;
310 }
311
312private:
313 static int32_t raw_read_bytes(void* id, void* data, int32_t bcount)
314 {
315 Importer* importer = static_cast<Importer*>(id);
316 uint8_t* outptr = static_cast<uint8_t*>(data);
317
318 if (importer->UngetcFlag)
319 {
320 *outptr++ = importer->UngetcChar;
321 importer->UngetcFlag = false;
322 bcount--;
323 }
324
325 const auto bytesToCopy =
326 std::min<int32_t>(bcount, importer->Size - importer->Offset);
327
328 std::memcpy(
329 outptr, static_cast<const uint8_t*>(importer->Data) + importer->Offset,
330 bytesToCopy);
331
332 outptr += bytesToCopy;
333 bcount -= bytesToCopy;
334
335 importer->Offset += bytesToCopy;
336
337 return static_cast<int32_t>(outptr - static_cast<uint8_t*>(data));
338 }
339
340 static int32_t raw_write_bytes(void* id, void* data, int32_t bcount)
341 {
342 return 0;
343 }
344
345 static int64_t raw_get_pos(void* id)
346 {
347 return static_cast<Importer*>(id)->Offset;
348 }
349
350 static int raw_set_pos_abs(void* id, int64_t pos)
351 {
352 return raw_set_pos_rel(id, pos, SEEK_SET);
353 }
354
355 static int raw_set_pos_rel(void* id, int64_t delta, int mode)
356 {
357 Importer* importer = static_cast<Importer*>(id);
358
359 switch (mode)
360 {
361 case SEEK_SET:
362 {
363 if (delta < 0 || delta > importer->Size)
364 return 1;
365 importer->Offset = delta;
366 break;
367 }
368 case SEEK_CUR:
369 {
370 const auto newOffset = delta + importer->Offset;
371 if (newOffset < 0 || newOffset > importer->Size)
372 return 1;
373 importer->Offset += delta;
374 break;
375 }
376 case SEEK_END:
377 {
378 if (delta > 0 || -delta > importer->Size)
379 return 1;
380 importer->Offset = importer->Size + delta;
381 break;
382 }
383 }
384
385 return 0;
386 }
387
388 static int raw_push_back_byte(void* id, int c)
389 {
390 Importer* importer = static_cast<Importer*>(id);
391
392 importer->UngetcChar = c;
393 importer->UngetcFlag = true;
394
395 return c;
396 }
397
398 static int64_t raw_get_length(void* id)
399 {
400 return static_cast<Importer*>(id)->Size;
401 }
402
403 static int raw_can_seek(void*)
404 {
405 return 1;
406 }
407
408 static int raw_close_stream(void*)
409 {
410 return 0;
411 }
412
413 WavpackStreamReader64 raw_reader { raw_read_bytes, raw_write_bytes,
414 raw_get_pos, raw_set_pos_abs,
415 raw_set_pos_rel, raw_push_back_byte,
416 raw_get_length, raw_can_seek,
417 nullptr, raw_close_stream };
418
419}; // struct Importer
420
421float GetFloatValue(int16_t value) noexcept
422{
423 return static_cast<float>(value) / std::numeric_limits<int16_t>::max();
424}
425
426float GetFloatValue(int32_t value) noexcept
427{
428 return static_cast<float>(value) / ((1 << 23) - 1);
429}
430
431float GetFloatValue(float value) noexcept
432{
433 return value;
434}
435
436template <typename T>
437void UpdateRMS (DecompressedBlock& block, const std::vector<T>& data)
438{
439 const auto samplesCount = data.size();
440 const auto sum256Count = (samplesCount + 255) / 256;
441 const auto sum64kCount = (samplesCount + 65535) / 65536;
442
443 block.Summary256.resize(sum256Count);
444 block.Summary64k.resize(sum64kCount);
445
446 auto& blockStats = block.BlockMinMaxRMS;
447
448 for (size_t i = 0; i < samplesCount; ++i)
449 {
450 const auto value = GetFloatValue(data[i]);
451
452 blockStats.Min = std::min(blockStats.Min, value);
453 blockStats.Max = std::max(blockStats.Max, value);
454 blockStats.RMS += value * value;
455
456 auto& summary256 = block.Summary256[i / 256];
457
458 summary256.Min = std::min(summary256.Min, value);
459 summary256.Max = std::max(summary256.Max, value);
460 summary256.RMS += value * value;
461
462 auto& summary64k = block.Summary64k[i / 65536];
463 summary64k.Min = std::min(summary64k.Min, value);
464 summary64k.Max = std::max(summary64k.Max, value);
465 summary64k.RMS += value * value;
466 }
467
468 block.BlockMinMaxRMS.RMS = std::sqrt(block.BlockMinMaxRMS.RMS / samplesCount);
469
470 auto samplesProcessed = 0;
471 for (auto& summary : block.Summary256)
472 {
473 const auto samplesToProcess = std::min<int>(256, samplesCount - samplesProcessed);
474 summary.RMS = std::sqrt(summary.RMS / samplesToProcess);
475 samplesProcessed += samplesToProcess;
476 }
477
478 samplesProcessed = 0;
479 for (auto& summary : block.Summary64k)
480 {
481 const auto samplesToProcess = std::min<int>(65536, samplesCount - samplesProcessed);
482 summary.RMS = std::sqrt(summary.RMS / samplesToProcess);
483 samplesProcessed += samplesToProcess;
484 }
485 }
486} // namespace
487
488std::vector<uint8_t> CompressBlock(const LockedBlock& block)
489{
490 Exporter exporter { block };
491 return exporter.Compress();
492}
493
494std::optional<DecompressedBlock>
495DecompressBlock(const void* data, const std::size_t size)
496{
497 if (data == nullptr || size == 0)
498 return {};
499
500 Importer importer { data, static_cast<int64_t>(size) };
501
502 if (!importer.Unpack())
503 return {};
504
505 DecompressedBlock result {};
506
507 result.BlockId = importer.BlockId;
508 result.Format = importer.Format;
509
510 const auto sampleSize = SAMPLE_SIZE(importer.Format);
511
512 result.Data.resize(importer.SamplesCount * sampleSize);
513
514 if (importer.Format == int16Sample)
515 {
516 std::memcpy(
517 result.Data.data(), importer.Int16Buffer.data(), result.Data.size());
518 UpdateRMS(result, importer.Int16Buffer);
519 }
520 else if (importer.Format == sampleFormat::int24Sample)
521 {
522 std::memcpy(
523 result.Data.data(), importer.Int32Buffer.data(), result.Data.size());
524 UpdateRMS(result, importer.Int32Buffer);
525 }
526 else if (importer.Format == floatSample)
527 {
528 std::memcpy(
529 result.Data.data(), importer.FloatBuffer.data(), result.Data.size());
530 UpdateRMS(result, importer.FloatBuffer);
531 }
532 else
533 {
534 assert(false);
535 return {};
536 }
537
538 return result;
539}
540
541} // namespace audacity::cloud::audiocom::sync
int min(int a, int b)
FromCharsResult FromChars(const char *buffer, const char *last, float &value) noexcept
Parse a string into a single precision floating point value, always uses the dot as decimal.
Definition: FromChars.cpp:153
Declare functions to convert numeric types to string representation.
constexpr sampleFormat int16Sample
Definition: SampleFormat.h:43
constexpr sampleFormat floatSample
Definition: SampleFormat.h:45
sampleFormat
The ordering of these values with operator < agrees with the order of increasing bit width.
Definition: SampleFormat.h:30
#define SAMPLE_SIZE(SampleFormat)
Definition: SampleFormat.h:52
#define SAMPLE_SIZE_DISK(SampleFormat)
Return the size on disk of one uncompressed sample (bytes)
Definition: SampleFormat.h:67
constexpr sampleFormat undefinedSample
Definition: SampleFormat.h:42
int id
Abstract base class used in importing a file.
Singleton class which actually imports the audio, using ImportPlugin objects that are registered by m...
Definition: Import.h:84
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
void UpdateRMS(DecompressedBlock &block, const std::vector< T > &data)
std::optional< DecompressedBlock > DecompressBlock(const void *data, const std::size_t size)
std::vector< uint8_t > CompressBlock(const LockedBlock &block)
const char * end(const char *str) noexcept
Definition: StringUtils.h:106
SizeType< float > Size
Alias for SizeType<float>
Definition: Size.h:174
__finl float_x4 __vecc sqrt(const float_x4 &a)
STL namespace.