Skip to content

Commit 45486e9

Browse files
committed
Catmull-Rom impl. #1063
1 parent aef7f71 commit 45486e9

File tree

2 files changed

+78
-24
lines changed

2 files changed

+78
-24
lines changed

src/Audio.cpp

Lines changed: 73 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
audio.cpp
44
55
Created on: Oct 28.2018 */char audioI2SVers[] ="\
6-
Version 3.3.1 ";
7-
/* Updated on: Jun 08.2025
6+
Version 3.3.1a ";
7+
/* Updated on: Jun 12.2025
88
99
Author: Wolle (schreibfaul1)
1010
Audio library for ESP32, ESP32-S3 or ESP32-P4
@@ -232,12 +232,13 @@ esp_err_t Audio::I2Sstart() {
232232
esp_err_t Audio::I2Sstop() {
233233
memset(m_outBuff.get(), 0, m_outbuffSize * sizeof(int16_t)); // Clear OutputBuffer
234234
memset(m_samplesBuff48K.get(), 0, m_samplesBuff48KSize * sizeof(int16_t)); // Clear samplesBuff48K
235+
std::fill(std::begin(m_inputHistory), std::end(m_inputHistory), 0); // Clear history in samplesBuff48K
235236
return i2s_channel_disable(m_i2s_tx_handle);
236237
}
237238
//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
238239
void Audio::zeroI2Sbuff(){
239240
uint8_t buff[2] = {0, 0}; // From IDF V5 there is no longer the zero_dma_buff() function.
240-
size_t bytes_loaded = 0; // As a replacement, we write a small amount of zeros in the buffer and thus reset the entire buffer.
241+
size_t bytes_loaded = 0; // As a replacement, we write a small amount of zeros in the buffer and thus reset the entire buffer.
241242
i2s_channel_preload_data(m_i2s_tx_handle, buff, 2, &bytes_loaded);
242243
}
243244
//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -321,7 +322,7 @@ void Audio::setDefaults() {
321322
m_M4A_sampleRate = 0;
322323
m_sumBytesDecoded = 0;
323324
m_vuLeft = m_vuRight = 0; // #835
324-
325+
std::fill(std::begin(m_inputHistory), std::end(m_inputHistory), 0);
325326
if(m_f_reset_m3u8Codec){m_m3u8Codec = CODEC_AAC;} // reset to default
326327
m_f_reset_m3u8Codec = true;
327328

@@ -2353,34 +2354,83 @@ bool Audio::pauseResume() {
23532354
return retVal;
23542355
}
23552356
//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2357+
size_t Audio::resampleTo48kStereo(const int16_t* input, size_t inputSamples) {
2358+
float ratio = static_cast<float>(m_sampleRate) / 48000.0f;
2359+
float cursor = m_resampleCursor;
2360+
2361+
// Anzahl Input-Samples + 3 History-Samples (vorherige 3 Stereo-Frames)
2362+
size_t extendedSamples = inputSamples + 3;
2363+
2364+
// Temporärer Buffer: History + aktueller Input
2365+
std::vector<int16_t> extendedInput(extendedSamples * 2);
2366+
2367+
// Historie an den Anfang kopieren (6 Werte = 3 Stereo-Samples)
2368+
memcpy(&extendedInput[0], m_inputHistory, 6 * sizeof(int16_t));
2369+
2370+
// Aktuelles Input danach einfügen
2371+
memcpy(&extendedInput[6], input, inputSamples * 2 * sizeof(int16_t));
2372+
2373+
size_t outputIndex = 0;
2374+
2375+
auto clipToInt16 = [](float value) -> int16_t {
2376+
if (value > 32767.0f){log_e("overflow +"); return 32767;}
2377+
if (value < -32768.0f) {log_e("overflow -");return -32768;}
2378+
return static_cast<int16_t>(value);
2379+
};
2380+
2381+
for (size_t inIdx = 1; inIdx < extendedSamples - 2; ++inIdx) {
2382+
int32_t xm1_l = clipToInt16(extendedInput[(inIdx - 1) * 2]);
2383+
int32_t x0_l = clipToInt16(extendedInput[(inIdx + 0) * 2]);
2384+
int32_t x1_l = clipToInt16(extendedInput[(inIdx + 1) * 2]);
2385+
int32_t x2_l = clipToInt16(extendedInput[(inIdx + 2) * 2]);
23562386

2357-
size_t Audio::resampleTo48kStereo(const int16_t* input, size_t inputFrames) {
2387+
int32_t xm1_r = clipToInt16(extendedInput[(inIdx - 1) * 2 + 1]);
2388+
int32_t x0_r = clipToInt16(extendedInput[(inIdx + 0) * 2 + 1]);
2389+
int32_t x1_r = clipToInt16(extendedInput[(inIdx + 1) * 2 + 1]);
2390+
int32_t x2_r = clipToInt16(extendedInput[(inIdx + 2) * 2 + 1]);
23582391

2359-
float exactOutputFrames = inputFrames * m_resampleRatio;;
2360-
size_t outputFrames = static_cast<size_t>(std::floor(exactOutputFrames + m_resampleError));
2361-
m_resampleError += exactOutputFrames - outputFrames;
2392+
while (cursor < 1.0f) {
2393+
float t = cursor;
23622394

2363-
std::vector<int16_t> output(outputFrames * 2);
2395+
// Catmull-Rom spline, construct a cubic curve
2396+
auto catmullRom = [](float t, float xm1, float x0, float x1, float x2) {
2397+
return 0.5f * (
2398+
(2.0f * x0) +
2399+
(-xm1 + x1) * t +
2400+
(2.0f * xm1 - 5.0f * x0 + 4.0f * x1 - x2) * t * t +
2401+
(-xm1 + 3.0f * x0 - 3.0f * x1 + x2) * t * t * t
2402+
);
2403+
};
23642404

2365-
for (size_t i = 0; i < outputFrames; ++i) {
2366-
float inFramePos = i / m_resampleRatio;
2367-
size_t idx = static_cast<size_t>(inFramePos);
2368-
float frac = inFramePos - idx;
23692405

2370-
size_t i1 = idx * 2;
2371-
size_t i2 = (idx + 1 < inputFrames) ? (idx + 1) * 2 : i1;
23722406

2373-
int16_t left1 = input[i1];
2374-
int16_t right1 = input[i1 + 1];
2375-
int16_t left2 = input[i2];
2376-
int16_t right2 = input[i2 + 1];
2407+
int16_t outLeft = static_cast<int16_t>(
2408+
catmullRom(t, xm1_l, x0_l, x1_l, x2_l)
2409+
);
2410+
int16_t outRight = static_cast<int16_t>(
2411+
catmullRom(t, xm1_r, x0_r, x1_r, x2_r)
2412+
);
23772413

2378-
m_samplesBuff48K[i * 2] = static_cast<int16_t>(left1 * (1.0f - frac) + left2 * frac);
2379-
m_samplesBuff48K[i * 2 + 1] = static_cast<int16_t>(right1 * (1.0f - frac) + right2 * frac);
2414+
m_samplesBuff48K[outputIndex * 2] = clipToInt16(outLeft);
2415+
m_samplesBuff48K[outputIndex * 2 + 1] = clipToInt16(outRight);
2416+
2417+
++outputIndex;
2418+
cursor += ratio;
2419+
}
2420+
2421+
cursor -= 1.0f;
23802422
}
23812423

2382-
return outputFrames;
2424+
// Letzte 3 Stereo-Samples als neue Historie sichern
2425+
for (int i = 0; i < 3; ++i) {
2426+
size_t idx = inputSamples - 3 + i;
2427+
m_inputHistory[i * 2] = input[idx * 2];
2428+
m_inputHistory[i * 2 + 1] = input[idx * 2 + 1];
2429+
}
2430+
m_resampleCursor = cursor;
2431+
return outputIndex;
23832432
}
2433+
23842434
//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
23852435
void IRAM_ATTR Audio::playChunk() {
23862436

@@ -2448,7 +2498,7 @@ void IRAM_ATTR Audio::playChunk() {
24482498

24492499
i2swrite:
24502500

2451-
err = i2s_channel_write(m_i2s_tx_handle, (int16_t*)m_samplesBuff48K.get() + count, samples48K * sampleSize, &i2s_bytesConsumed, 10);
2501+
err = i2s_channel_write(m_i2s_tx_handle, (int16_t*)m_samplesBuff48K.get() + count, samples48K * sampleSize, &i2s_bytesConsumed, 50);
24522502
if( ! (err == ESP_OK || err == ESP_ERR_TIMEOUT)) goto exit;
24532503
samples48K -= i2s_bytesConsumed / sampleSize;
24542504
count += i2s_bytesConsumed / 2;

src/Audio.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,7 @@ class Audio : private AudioBuffer{
725725
int8_t m_balance = 0; // -16 (mute left) ... +16 (mute right)
726726
uint16_t m_vol = 21; // volume
727727
uint16_t m_vol_steps = 21; // default
728+
int16_t m_inputHistory[6] = {0}; // used in resampleTo48kStereo()
728729
double m_limit_left = 0; // limiter 0 ... 1, left channel
729730
double m_limit_right = 0; // limiter 0 ... 1, right channel
730731
uint8_t m_timeoutCounter = 0; // timeout counter
@@ -746,7 +747,6 @@ class Audio : private AudioBuffer{
746747
uint8_t m_M4A_objectType = 0; // set in read_M4A_Header
747748
uint8_t m_M4A_chConfig = 0; // set in read_M4A_Header
748749
uint16_t m_M4A_sampleRate = 0; // set in read_M4A_Header
749-
750750
int16_t m_validSamples = {0}; // #144
751751
int16_t m_curSample{0};
752752
uint16_t m_dataMode{0}; // Statemaschine
@@ -812,6 +812,10 @@ class Audio : private AudioBuffer{
812812
float m_audioCurrentTime = 0;
813813
float m_resampleError = 0.0f;
814814
float m_resampleRatio = 1.0f; // resample ratio for e.g. 44.1kHz to 48kHz
815+
float m_resampleCursor = 0.0f; // next frac in resampleTo48kStereo
816+
817+
818+
815819
uint32_t m_audioDataStart = 0; // in bytes
816820
size_t m_audioDataSize = 0; //
817821
size_t m_ibuffSize = 0; // log buffer size for audio_info()

0 commit comments

Comments
 (0)