Skip to content

Commit adb4530

Browse files
committed
DRAFT CodecDSF
1 parent ed09034 commit adb4530

File tree

1 file changed

+78
-60
lines changed

1 file changed

+78
-60
lines changed

src/AudioTools/AudioCodecs/CodecDSF.h

Lines changed: 78 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
*/
2323

2424
#pragma once
25-
//#pragma GCC optimize("Ofast")
25+
// #pragma GCC optimize("Ofast")
2626
#pragma GCC optimize("O3")
2727

2828
#include "AudioTools/AudioCodecs/AudioCodecsBase.h"
@@ -51,9 +51,7 @@ namespace audio_tools {
5151
*/
5252
struct DSFMetadata : public AudioInfo {
5353
DSFMetadata() = default;
54-
DSFMetadata(int rate) {
55-
sample_rate = rate;
56-
}
54+
DSFMetadata(int rate) { sample_rate = rate; }
5755
uint32_t dsd_sample_rate =
5856
0; ///< DSD sample rate (e.g. 2822400 Hz for DSD64)
5957
uint64_t dsd_data_bytes = 0; ///< Size of DSD bitstream data in bytes
@@ -64,6 +62,7 @@ struct DSFMetadata : public AudioInfo {
6462
DSD_BUFFER_SIZE; ///< Internal buffer size for DSD processing
6563
float filter_q = 1.41f;
6664
float filter_cutoff = 0.4f; ///< Cutoff frequency as fraction of Nyquist
65+
int output_buffer_size = 1024;
6766
};
6867

6968
/**
@@ -140,20 +139,18 @@ class DSFDecoder : public AudioDecoder {
140139
AudioDecoder::setAudioInfo(from);
141140
meta.copyFrom(from);
142141
// Ensure PCM buffer is allocated based on the new audio info
143-
int frame_size = meta.bits_per_sample / 8 * meta.channels;
144-
if (meta.bits_per_sample == 24) frame_size = 4 * meta.channels;
145-
146-
pcmBuffer.resize(frame_size); // Allocate PCM buffer (up to 32-bit stereo)
142+
int buffer_size = getOutputBufferSize();
143+
pcmBuffer.resize(buffer_size);
147144
channelAccum.resize(meta.channels);
148145
channelIntegrator.resize(meta.channels);
149-
146+
150147
// Initialize integrator states
151148
for (int i = 0; i < meta.channels; i++) {
152149
channelIntegrator[i] = 0.0f;
153150
}
154-
155-
setupDecimationStep();
151+
156152
setupTargetPCMRate();
153+
setupDecimationStep();
157154
}
158155

159156
/**
@@ -246,19 +243,30 @@ class DSFDecoder : public AudioDecoder {
246243
size_t filePos; ///< Current position in DSF file
247244

248245
// Processing buffers and state
249-
Vector<uint8_t> pcmBuffer; ///< Buffer for PCM output samples - supports
250-
///< multi-channel up to 32-bit
246+
SingleBuffer<uint8_t> pcmBuffer{0}; ///< Buffer for PCM output samples -
247+
///< supports multi-channel up to 32-bit
251248
Vector<float> channelAccum; ///< Accumulator for each channel during DSD to
252249
///< PCM conversion
253250
Vector<LowPassFilter<float>>
254251
channelFilters; ///< Anti-aliasing filters for each channel
255252
RingBuffer<uint8_t> dsdBuffer{0}; ///< Ring buffer for DSD data
256253
uint32_t decimationStep; ///< Decimation factor for DSD to PCM conversion
257-
Vector<float> channelIntegrator; ///< Integrator state for each channel (for better DSD conversion)
254+
Vector<float> channelIntegrator; ///< Integrator state for each channel (for
255+
///< better DSD conversion)
258256

259257
// Metadata
260258
DSFMetadata meta; ///< Extracted DSF file metadata
261259

260+
/// The buffer size is defined in the metadata: it must be at least 1 frame
261+
int getOutputBufferSize() {
262+
int frame_size = meta.bits_per_sample / 8 * meta.channels;
263+
if (meta.bits_per_sample == 24) frame_size = 4 * meta.channels;
264+
int buffer_size = frame_size;
265+
if (meta.output_buffer_size > buffer_size)
266+
buffer_size = meta.output_buffer_size;
267+
return buffer_size;
268+
}
269+
262270
/**
263271
* @brief Process header data until header is complete or data is exhausted
264272
* @param data Input data buffer
@@ -314,7 +322,7 @@ class DSFDecoder : public AudioDecoder {
314322
// Buffer as much DSD data as possible
315323
bytesProcessed += bufferDSDData(data, len, startPos);
316324

317-
// Convert buffered DSD data to PCM output
325+
// Convert buffered DSD data to PCM output
318326
convertDSDToPCM();
319327

320328
return bytesProcessed;
@@ -331,25 +339,24 @@ class DSFDecoder : public AudioDecoder {
331339
* data is consumed or the buffer becomes full.
332340
*/
333341
size_t bufferDSDData(const uint8_t* data, size_t len, size_t startPos) {
334-
size_t bytesBuffered = 0;
335-
size_t pos = startPos;
336-
337-
while (pos < len && !dsdBuffer.isFull()) {
338-
dsdBuffer.write(data[pos]);
339-
filePos++;
340-
pos++;
341-
bytesBuffered++;
342+
int write_len = len - startPos;
343+
if (write_len > dsdBuffer.availableForWrite()) {
344+
write_len = dsdBuffer.availableForWrite();
342345
}
346+
dsdBuffer.writeArray(data + startPos, write_len);
347+
filePos += write_len;
343348

344-
return bytesBuffered;
349+
return write_len;
345350
}
346351

347352
/**
348353
* @brief Convert buffered DSD data to PCM samples and output them
349354
*
350-
* Performs the core DSD to PCM conversion process using integrator-based approach:
355+
* Performs the core DSD to PCM conversion process using integrator-based
356+
* approach:
351357
* 1. Integrates DSD bits over the decimation period for each channel
352-
* 2. Converts DSD bits to analog values (-1 or +1) with proper delta-sigma handling
358+
* 2. Converts DSD bits to analog values (-1 or +1) with proper delta-sigma
359+
* handling
353360
* 3. Applies low-pass filtering to remove high-frequency noise
354361
* 4. Converts filtered values to PCM samples
355362
* 5. Outputs PCM samples for all channels
@@ -380,14 +387,14 @@ class DSFDecoder : public AudioDecoder {
380387
// Use integrator-based approach for better DSD conversion
381388
for (int bit = 0; bit < 8; bit++) {
382389
int channelBit = (dsdByte >> (7 - bit)) & 1; // MSB first in DSF
383-
390+
384391
// Delta-sigma integration: accumulate the difference
385392
channelIntegrator[ch] += channelBit ? 1.0f : -1.0f;
386-
393+
387394
// Apply decay to prevent DC buildup
388395
channelIntegrator[ch] *= 0.9999f;
389396
}
390-
397+
391398
// Add integrated value to channel accumulator
392399
channelAccum[ch] += channelIntegrator[ch];
393400
samplesProcessed += 8;
@@ -401,7 +408,7 @@ class DSFDecoder : public AudioDecoder {
401408
for (int ch = 0; ch < meta.channels; ch++) {
402409
if (samplesPerChannel > 0) {
403410
// Normalize by sample count and apply scaling factor
404-
channelAccum[ch] = channelAccum[ch] / (samplesPerChannel * 8.0f);
411+
channelAccum[ch] = clip(channelAccum[ch] / (samplesPerChannel * 8.0f));
405412
}
406413

407414
// Apply low-pass filter to remove high-frequency noise
@@ -410,16 +417,21 @@ class DSFDecoder : public AudioDecoder {
410417
}
411418

412419
// Convert to PCM sample and store in buffer
413-
convertToPCMSample(clip(channelAccum[ch]), ch);
420+
writePCMSample(clip(channelAccum[ch]));
414421
}
415422

416423
// Output the PCM samples for all channels
417-
size_t frameSize = pcmBuffer.size();
418-
size_t written =
419-
getOutput()->write((uint8_t*)pcmBuffer.data(), frameSize);
420-
if (written != frameSize) {
421-
LOGE("Failed to write PCM samples: expected %zu bytes, wrote %zu bytes",
422-
frameSize, written);
424+
if (pcmBuffer.isFull()) {
425+
size_t frameSize = pcmBuffer.available();
426+
size_t written =
427+
getOutput()->write((uint8_t*)pcmBuffer.data(), frameSize);
428+
if (written != frameSize) {
429+
LOGE(
430+
"Failed to write PCM samples: expected %zu bytes, wrote %zu "
431+
"bytes",
432+
frameSize, written);
433+
}
434+
pcmBuffer.reset();
423435
}
424436
}
425437
}
@@ -443,14 +455,16 @@ class DSFDecoder : public AudioDecoder {
443455
*
444456
* Initializes anti-aliasing filters for each audio channel with appropriate
445457
* cutoff frequency (40% of Nyquist frequency) for the current sample rate.
446-
* This ensures proper anti-aliasing performance during DSD to PCM conversion.
458+
* This ensures proper anti-aliasing performance during DSD to PCM
459+
* conversion.
447460
*/
448461
void setupTargetPCMRate() {
449462
TRACEI();
450463

451464
// Initialize filters for the correct number of channels
452465
if (meta.sample_rate > 0 && meta.channels > 0) {
453-
float cutoffFreq = meta.sample_rate * meta.filter_cutoff; // 40% of Nyquist frequency
466+
float cutoffFreq =
467+
meta.sample_rate * meta.filter_cutoff; // 40% of Nyquist frequency
454468
channelFilters.resize(meta.channels);
455469
for (int i = 0; i < meta.channels; i++) {
456470
channelFilters[i].begin(cutoffFreq, meta.sample_rate, meta.filter_q);
@@ -461,32 +475,35 @@ class DSFDecoder : public AudioDecoder {
461475
/**
462476
* @brief Calculate optimal decimation step for DSD to PCM conversion
463477
*
464-
* Calculates the decimation factor as the ratio of DSD sample rate to target
465-
* PCM sample rate. Clamps the value between 64 and 512 to ensure reasonable
466-
* processing efficiency and audio quality while maintaining good anti-aliasing
467-
* performance.
478+
* Calculates the decimation factor as the ratio of DSD sample rate to
479+
* target PCM sample rate. Clamps the value between 64 and 512 to ensure
480+
* reasonable processing efficiency and audio quality while maintaining good
481+
* anti-aliasing performance.
468482
*/
469483
void setupDecimationStep() {
470484
TRACEI();
471485
if (meta.sample_rate == 0 || meta.dsd_sample_rate == 0) {
472-
LOGE("Invalid sample rates: DSD=%u, PCM=%u", (unsigned)meta.dsd_sample_rate, (unsigned)meta.sample_rate);
486+
LOGE("Invalid sample rates: DSD=%u, PCM=%u",
487+
(unsigned)meta.dsd_sample_rate, (unsigned)meta.sample_rate);
473488
return;
474489
}
475-
490+
476491
decimationStep = meta.dsd_sample_rate / meta.sample_rate;
477492
if (decimationStep < 64) {
478-
LOGW("Decimation step %u too low, setting to 64", (unsigned)decimationStep);
493+
LOGW("Decimation step %u too low, setting to 64",
494+
(unsigned)decimationStep);
479495
decimationStep = 64;
480496
}
481497
if (decimationStep > 512) {
482-
LOGW("Decimation step %u too high, setting to 512", (unsigned)decimationStep);
498+
LOGW("Decimation step %u too high, setting to 512",
499+
(unsigned)decimationStep);
483500
decimationStep = 512;
484501
}
485-
502+
486503
// Ensure decimation step is multiple of 8 for clean byte processing
487504
decimationStep = (decimationStep / 8) * 8;
488505
if (decimationStep < 64) decimationStep = 64;
489-
506+
490507
LOGI("Decimation step set to %u for DSD rate %u and target PCM rate %u",
491508
(unsigned)decimationStep, (unsigned)meta.dsd_sample_rate,
492509
(unsigned)meta.sample_rate);
@@ -503,7 +520,8 @@ class DSFDecoder : public AudioDecoder {
503520
*/
504521
bool hasEnoughData() {
505522
// DSF uses byte interleaving: each decimation step needs enough bytes
506-
// to cover all channels. Each byte contains 8 DSD samples for one channel.
523+
// to cover all channels. Each byte contains 8 DSD samples for one
524+
// channel.
507525
int bytesPerDecimationStep = (decimationStep / 8) * meta.channels;
508526
if (bytesPerDecimationStep < meta.channels)
509527
bytesPerDecimationStep = meta.channels;
@@ -516,28 +534,28 @@ class DSFDecoder : public AudioDecoder {
516534
* @param filteredValue The filtered DSD value (range -1.0 to 1.0)
517535
* @param channel Channel index (0 for left/mono, 1 for right)
518536
*/
519-
void convertToPCMSample(float filteredValue, int channel) {
537+
void writePCMSample(float filteredValue) {
520538
switch (meta.bits_per_sample) {
521539
case 8: {
522-
int8_t* buffer8 = reinterpret_cast<int8_t*>(pcmBuffer.data());
523-
buffer8[channel] = static_cast<int8_t>(filteredValue * 127.0f);
540+
int8_t buffer8 = static_cast<int8_t>(filteredValue * 127.0f);
541+
pcmBuffer.write(buffer8);
524542
break;
525543
}
526544
case 16: {
527-
int16_t* buffer16 = reinterpret_cast<int16_t*>(pcmBuffer.data());
528-
buffer16[channel] = static_cast<int16_t>(filteredValue * 32767.0f);
545+
int16_t buffer16 = static_cast<int16_t>(filteredValue * 32767.0f);
546+
pcmBuffer.writeArray((uint8_t*)&buffer16, sizeof(int16_t));
529547
break;
530548
}
531549
case 24: {
532-
int24_t* buffer24 = reinterpret_cast<int24_t*>(pcmBuffer.data());
533-
buffer24[channel] =
550+
int24_t buffer24 =
534551
static_cast<int24_t>(filteredValue * 8388607.0f); // 2^23 - 1
552+
pcmBuffer.writeArray((uint8_t*)&buffer24, sizeof(int24_t));
535553
break;
536554
}
537555
case 32: {
538-
int32_t* buffer32 = reinterpret_cast<int32_t*>(pcmBuffer.data());
539-
buffer32[channel] =
540-
static_cast<int32_t>(filteredValue * 2147483647.0f); // 2^31 - 1
556+
int32_t buffer32 =
557+
static_cast<int32_t>(filteredValue * 2147483647.0f); // 2^31 -
558+
pcmBuffer.writeArray((uint8_t*)&buffer32, sizeof(int32_t));
541559
break;
542560
}
543561
default:

0 commit comments

Comments
 (0)