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 &&
202 //The negative BlockId means silenced blocks and it's absolute value encodes it's length
203 //Older projects used to store them on the cloud as regular blocks.
204 ((BlockId >= 0) || (BlockId < 0 && -BlockId == SamplesCount));
205 }
206
207 Importer(const void* data, const int64_t size)
208 : Data { data }
209 , Size { size }
210 {
211 if (Data == nullptr || Size == 0)
212 return;
213
214 char error[81];
215
216 Context = WavpackOpenFileInputEx64(
217 &raw_reader, this, nullptr, error, OPEN_DSD_AS_PCM | OPEN_TAGS, 0);
218
219 if (Context == nullptr)
220 return;
221
222 Mode = WavpackGetMode(Context);
223 SamplesCount = WavpackGetNumSamples(Context);
224
225 const auto formatString = ReadTag("FORMAT");
226 unsigned format {};
227 auto result = FromChars(
228 formatString.c_str(), formatString.c_str() + formatString.size(),
229 format);
230 if (result.ec == std::errc {})
231 Format = static_cast<sampleFormat>(format);
232
233 auto blockIdString = ReadTag("BLOCK_ID");
234 FromChars(
235 blockIdString.c_str(), blockIdString.c_str() + blockIdString.size(),
236 BlockId);
237 }
238
239 std::string ReadTag(const char* tagName)
240 {
241 if (Context == nullptr)
242 return {};
243
244 const auto tagLength = WavpackGetTagItem(Context, tagName, nullptr, 0);
245
246 std::string tag;
247
248 if (tagLength > 0)
249 {
250 tag.resize(tagLength + 1);
251 WavpackGetTagItem(Context, tagName, tag.data(), tagLength + 1);
252 tag.resize(tagLength);
253 }
254
255 return tag;
256 }
257
259 {
260 if (Context != nullptr)
261 WavpackCloseFile(Context);
262 }
263
264 bool Unpack()
265 {
266 if (!IsValid())
267 return false;
268
269 Int32Buffer.resize(SamplesCount);
270
271 if (Format == int16Sample)
272 Int16Buffer.resize(SamplesCount);
273 else if (Format == floatSample)
274 FloatBuffer.resize(SamplesCount);
275
276 const auto samplesRead =
277 WavpackUnpackSamples(Context, Int32Buffer.data(), SamplesCount);
278
279 if (samplesRead != SamplesCount)
280 return false;
281
282 const bool floatSamples = (Mode & MODE_FLOAT) == MODE_FLOAT;
283
284 if (floatSamples)
285 {
286 FloatBuffer.resize(SamplesCount);
287 std::memcpy(
288 FloatBuffer.data(), Int32Buffer.data(),
289 SamplesCount * sizeof(float));
290 }
291 else if (Format == int16Sample)
292 {
293 const auto bytesPerSample = WavpackGetBytesPerSample(Context);
294
295 if (bytesPerSample == 1)
296 {
297 for (size_t i = 0; i < SamplesCount; ++i)
298 Int16Buffer[i] = static_cast<int16_t>(Int32Buffer[i]) << 8;
299 }
300 else if (bytesPerSample == 2)
301 {
302 for (size_t i = 0; i < SamplesCount; ++i)
303 Int16Buffer[i] = static_cast<int16_t>(Int32Buffer[i]);
304 }
305 else
306 {
307 assert(false);
308 return false;
309 }
310 }
311
312 return true;
313 }
314
315private:
316 static int32_t raw_read_bytes(void* id, void* data, int32_t bcount)
317 {
318 Importer* importer = static_cast<Importer*>(id);
319 uint8_t* outptr = static_cast<uint8_t*>(data);
320
321 if (importer->UngetcFlag)
322 {
323 *outptr++ = importer->UngetcChar;
324 importer->UngetcFlag = false;
325 bcount--;
326 }
327
328 const auto bytesToCopy =
329 std::min<int32_t>(bcount, importer->Size - importer->Offset);
330
331 std::memcpy(
332 outptr, static_cast<const uint8_t*>(importer->Data) + importer->Offset,
333 bytesToCopy);
334
335 outptr += bytesToCopy;
336 bcount -= bytesToCopy;
337
338 importer->Offset += bytesToCopy;
339
340 return static_cast<int32_t>(outptr - static_cast<uint8_t*>(data));
341 }
342
343 static int32_t raw_write_bytes(void* id, void* data, int32_t bcount)
344 {
345 return 0;
346 }
347
348 static int64_t raw_get_pos(void* id)
349 {
350 return static_cast<Importer*>(id)->Offset;
351 }
352
353 static int raw_set_pos_abs(void* id, int64_t pos)
354 {
355 return raw_set_pos_rel(id, pos, SEEK_SET);
356 }
357
358 static int raw_set_pos_rel(void* id, int64_t delta, int mode)
359 {
360 Importer* importer = static_cast<Importer*>(id);
361
362 switch (mode)
363 {
364 case SEEK_SET:
365 {
366 if (delta < 0 || delta > importer->Size)
367 return 1;
368 importer->Offset = delta;
369 break;
370 }
371 case SEEK_CUR:
372 {
373 const auto newOffset = delta + importer->Offset;
374 if (newOffset < 0 || newOffset > importer->Size)
375 return 1;
376 importer->Offset += delta;
377 break;
378 }
379 case SEEK_END:
380 {
381 if (delta > 0 || -delta > importer->Size)
382 return 1;
383 importer->Offset = importer->Size + delta;
384 break;
385 }
386 }
387
388 return 0;
389 }
390
391 static int raw_push_back_byte(void* id, int c)
392 {
393 Importer* importer = static_cast<Importer*>(id);
394
395 importer->UngetcChar = c;
396 importer->UngetcFlag = true;
397
398 return c;
399 }
400
401 static int64_t raw_get_length(void* id)
402 {
403 return static_cast<Importer*>(id)->Size;
404 }
405
406 static int raw_can_seek(void*)
407 {
408 return 1;
409 }
410
411 static int raw_close_stream(void*)
412 {
413 return 0;
414 }
415
416 WavpackStreamReader64 raw_reader { raw_read_bytes, raw_write_bytes,
417 raw_get_pos, raw_set_pos_abs,
418 raw_set_pos_rel, raw_push_back_byte,
419 raw_get_length, raw_can_seek,
420 nullptr, raw_close_stream };
421
422}; // struct Importer
423
424float GetFloatValue(int16_t value) noexcept
425{
426 return static_cast<float>(value) / std::numeric_limits<int16_t>::max();
427}
428
429float GetFloatValue(int32_t value) noexcept
430{
431 return static_cast<float>(value) / ((1 << 23) - 1);
432}
433
434float GetFloatValue(float value) noexcept
435{
436 return value;
437}
438
439template <typename T>
440void UpdateRMS (DecompressedBlock& block, const std::vector<T>& data)
441{
442 const auto samplesCount = data.size();
443 const auto sum256Count = (samplesCount + 255) / 256;
444 const auto sum64kCount = (samplesCount + 65535) / 65536;
445
446 block.Summary256.resize(sum256Count);
447 block.Summary64k.resize(sum64kCount);
448
449 auto& blockStats = block.BlockMinMaxRMS;
450
451 for (size_t i = 0; i < samplesCount; ++i)
452 {
453 const auto value = GetFloatValue(data[i]);
454
455 blockStats.Min = std::min(blockStats.Min, value);
456 blockStats.Max = std::max(blockStats.Max, value);
457 blockStats.RMS += value * value;
458
459 auto& summary256 = block.Summary256[i / 256];
460
461 summary256.Min = std::min(summary256.Min, value);
462 summary256.Max = std::max(summary256.Max, value);
463 summary256.RMS += value * value;
464
465 auto& summary64k = block.Summary64k[i / 65536];
466 summary64k.Min = std::min(summary64k.Min, value);
467 summary64k.Max = std::max(summary64k.Max, value);
468 summary64k.RMS += value * value;
469 }
470
471 block.BlockMinMaxRMS.RMS = std::sqrt(block.BlockMinMaxRMS.RMS / samplesCount);
472
473 auto samplesProcessed = 0;
474 for (auto& summary : block.Summary256)
475 {
476 const auto samplesToProcess = std::min<int>(256, samplesCount - samplesProcessed);
477 summary.RMS = std::sqrt(summary.RMS / samplesToProcess);
478 samplesProcessed += samplesToProcess;
479 }
480
481 samplesProcessed = 0;
482 for (auto& summary : block.Summary64k)
483 {
484 const auto samplesToProcess = std::min<int>(65536, samplesCount - samplesProcessed);
485 summary.RMS = std::sqrt(summary.RMS / samplesToProcess);
486 samplesProcessed += samplesToProcess;
487 }
488 }
489} // namespace
490
491std::vector<uint8_t> CompressBlock(const LockedBlock& block)
492{
493 Exporter exporter { block };
494 return exporter.Compress();
495}
496
497std::optional<DecompressedBlock>
498DecompressBlock(const void* data, const std::size_t size)
499{
500 if (data == nullptr || size == 0)
501 return {};
502
503 Importer importer { data, static_cast<int64_t>(size) };
504
505 if (!importer.Unpack())
506 return {};
507
508 DecompressedBlock result {};
509
510 result.BlockId = importer.BlockId;
511 result.Format = importer.Format;
512
513 const auto sampleSize = SAMPLE_SIZE(importer.Format);
514
515 result.Data.resize(importer.SamplesCount * sampleSize);
516
517 if (importer.Format == int16Sample)
518 {
519 std::memcpy(
520 result.Data.data(), importer.Int16Buffer.data(), result.Data.size());
521 UpdateRMS(result, importer.Int16Buffer);
522 }
523 else if (importer.Format == sampleFormat::int24Sample)
524 {
525 std::memcpy(
526 result.Data.data(), importer.Int32Buffer.data(), result.Data.size());
527 UpdateRMS(result, importer.Int32Buffer);
528 }
529 else if (importer.Format == floatSample)
530 {
531 std::memcpy(
532 result.Data.data(), importer.FloatBuffer.data(), result.Data.size());
533 UpdateRMS(result, importer.FloatBuffer);
534 }
535 else
536 {
537 assert(false);
538 return {};
539 }
540
541 return result;
542}
543
544} // 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.