@@ -131,9 +131,10 @@ static volatile bool disableSoundProcessing = false; // if true, sound proc
131131static uint8_t audioSyncEnabled = AUDIOSYNC_NONE; // bit field: bit 0 - send, bit 1 - receive, bit 2 - use local if not receiving
132132static bool audioSyncSequence = true ; // if true, the receiver will drop out-of-sequence packets
133133static uint8_t audioSyncPurge = 1 ; // 0: process each packet (don't purge); 1: auto-purge old packets; 2: only process latest received packet (always purge)
134- static bool udpSyncConnected = false ; // UDP connection status -> true if connected to multicast group
134+ static bool udpSyncConnected = false ; // UDP connection status -> true if connected to multicast group
135+ static volatile bool isOOM = false ; // FFTask: not enough memory for buffers (audio processing failed to start)
135136
136- #define NUM_GEQ_CHANNELS 16 // number of frequency channels. Don't change !!
137+ #define NUM_GEQ_CHANNELS 16 // number of frequency channels. Don't change !!
137138
138139// audioreactive variables
139140#ifdef ARDUINO_ARCH_ESP32
@@ -454,13 +455,13 @@ static bool alocateFFTBuffers(void) {
454455 USER_PRINT (F (" \n Free heap " )); USER_PRINTLN (ESP.getFreeHeap ());
455456 #endif
456457
457- if (vReal) free (vReal); // should not happen
458- if (vImag) free (vImag); // should not happen
459- if ((vReal = (float *) calloc (samplesFFT, sizeof (float ))) == nullptr ) return false ; // calloc or die
460- if ((vImag = (float *) calloc (samplesFFT, sizeof (float ))) == nullptr ) return false ;
458+ if (vReal) d_free (vReal); vReal = nullptr ; // should not happen
459+ if (vImag) d_free (vImag); vImag = nullptr ; // should not happen
460+ if ((vReal = (float *) d_calloc (samplesFFT, sizeof (float ))) == nullptr ) return false ; // calloc or die
461+ if ((vImag = (float *) d_calloc (samplesFFT, sizeof (float ))) == nullptr ) return false ;
461462#ifdef FFT_MAJORPEAK_HUMAN_EAR
462- if (pinkFactors) free (pinkFactors);
463- if ((pinkFactors = (float *) calloc (samplesFFT, sizeof (float ))) == nullptr ) return false ;
463+ if (pinkFactors) p_free (pinkFactors);
464+ if ((pinkFactors = (float *) p_calloc (samplesFFT, sizeof (float ))) == nullptr ) return false ;
464465#endif
465466
466467 #ifdef SR_DEBUG
@@ -472,6 +473,27 @@ static bool alocateFFTBuffers(void) {
472473 return (true ); // success
473474}
474475
476+ // de-allocate FFT sample buffers from heap
477+ static void destroyFFTBuffers (bool panicOOM) {
478+ #ifdef FFT_MAJORPEAK_HUMAN_EAR
479+ if (pinkFactors) p_free (pinkFactors); pinkFactors = nullptr ;
480+ #endif
481+ if (vImag) d_free (vImag); vImag = nullptr ;
482+ if (vReal) d_free (vReal); vReal = nullptr ;
483+
484+ if (panicOOM && !isOOM) { // notify user
485+ isOOM = true ;
486+ errorFlag = ERR_LOW_MEM;
487+ USER_PRINTLN (" AR startup failed - out of memory!" );
488+ }
489+ #ifdef SR_DEBUG
490+ USER_PRINTLN (" \n destroyFFTBuffers() completed successfully." );
491+ USER_PRINT (F (" Free heap: " )); USER_PRINTLN (ESP.getFreeHeap ());
492+ USER_FLUSH ();
493+ #endif
494+ }
495+
496+
475497// High-Pass "DC blocker" filter
476498// see https://www.dsprelated.com/freebooks/filters/DC_Blocker.html
477499static void runDCBlocker (uint_fast16_t numSamples, float *sampleBuffer) {
@@ -488,6 +510,22 @@ static void runDCBlocker(uint_fast16_t numSamples, float *sampleBuffer) {
488510 }
489511}
490512
513+ //
514+ // FFT runner - "return" from a task function causes crash. This wrapper keeps the task alive
515+ //
516+ void runFFTcode (void * parameter) __attribute__((noreturn,used));
517+ void runFFTcode (void * parameter) {
518+ bool firstFail = true ; // prevents flood of warnings
519+ do {
520+ if (!disableSoundProcessing) {
521+ FFTcode (parameter);
522+ if (firstFail) {USER_PRINTLN (F (" warning: unexpected exit of FFT main task." ));}
523+ firstFail = false ;
524+ } else firstFail = true ; // re-enable warning message
525+ vTaskDelay (1000 ); // if we arrive here, FFcode has returned due to OOM. Wait a bit, then try again.
526+ } while (true );
527+ }
528+
491529//
492530// FFT main task
493531//
@@ -511,13 +549,18 @@ void FFTcode(void * parameter)
511549 static float * oldSamples = nullptr ; // previous 50% of samples
512550 static bool haveOldSamples = false ; // for sliding window FFT
513551 bool usingOldSamples = false ;
514- if (!oldSamples) oldSamples = (float *) calloc (samplesFFT_2, sizeof (float )); // allocate on first run
515- if (!oldSamples) { disableSoundProcessing = true ; return ; } // no memory -> die
552+ if (!oldSamples) oldSamples = (float *) d_calloc (samplesFFT_2, sizeof (float )); // allocate on first run
553+ if (!oldSamples) { disableSoundProcessing = true ; haveOldSamples = false ; destroyFFTBuffers ( true ); return ; } // no memory -> die
516554#endif
517555
518556 bool success = true ;
519557 if ((vReal == nullptr ) || (vImag == nullptr )) success = alocateFFTBuffers (); // allocate sample buffers on first run
520- if (success == false ) { disableSoundProcessing = true ; return ; } // no memory -> die
558+ if (success == false ) {
559+ // no memory -> clean up heap, then suspend
560+ disableSoundProcessing = true ;
561+ destroyFFTBuffers (true );
562+ return ;
563+ }
521564
522565 // create FFT object - we have to do if after allocating buffers
523566#if defined(FFT_LIB_REV) && FFT_LIB_REV > 0x19
@@ -527,7 +570,8 @@ void FFTcode(void * parameter)
527570 // recommended version optimized by @softhack007 (API version 1.9)
528571 #if defined(WLED_ENABLE_HUB75MATRIX) && defined(CONFIG_IDF_TARGET_ESP32)
529572 static float * windowWeighingFactors = nullptr ;
530- if (!windowWeighingFactors) windowWeighingFactors = (float *) calloc (samplesFFT, sizeof (float )); // cache for FFT windowing factors - use heap
573+ if (!windowWeighingFactors) windowWeighingFactors = (float *) d_calloc (samplesFFT, sizeof (float )); // cache for FFT windowing factors - use heap
574+ if (!windowWeighingFactors) { disableSoundProcessing = true ; haveOldSamples = false ; destroyFFTBuffers (true ); return ; } // alloc failed
531575 #else
532576 static float windowWeighingFactors[samplesFFT] = {0 .0f }; // cache for FFT windowing factors - use global RAM
533577 #endif
@@ -1921,6 +1965,7 @@ class AudioReactive : public Usermod {
19211965 void setup () override
19221966 {
19231967 disableSoundProcessing = true ; // just to be sure
1968+ isOOM = false ;
19241969 if (!initDone) {
19251970 // usermod exchangeable data
19261971 // we will assign all usermod exportable data here as pointers to original variables or arrays and allocate memory for pointers
@@ -2497,11 +2542,12 @@ class AudioReactive : public Usermod {
24972542 vTaskResume (FFT_Task);
24982543 connected (); // resume UDP
24992544 } else {
2545+ isOOM = false ;
25002546 if (audioSource) // WLEDMM only create FFT task if we have a valid audio source
25012547// xTaskCreatePinnedToCore(
25022548// xTaskCreate( // no need to "pin" this task to core #0
25032549 xTaskCreateUniversal (
2504- FFTcode, // Function to implement the task
2550+ runFFTcode, // Function to implement the task
25052551 " FFT" , // Name of the task
25062552 3592 , // Stack size in words // 3592 leaves 800-1024 bytes of task stack free
25072553 NULL , // Task input parameter
@@ -2646,7 +2692,7 @@ class AudioReactive : public Usermod {
26462692#else // ESP32 only
26472693 } else {
26482694 // Analog or I2S digital input
2649- if (audioSource && (audioSource->isInitialized ())) {
2695+ if (audioSource && (audioSource->isInitialized ()) && !isOOM ) {
26502696 // audio source successfully configured
26512697 if (audioSource->getType () == AudioSource::Type_I2SAdc) {
26522698 infoArr.add (F (" ADC analog" ));
@@ -2667,7 +2713,8 @@ class AudioReactive : public Usermod {
26672713 } else {
26682714 // error during audio source setup
26692715 infoArr.add (F (" not initialized" ));
2670- if (dmType < 254 ) infoArr.add (F (" - check pin settings" ));
2716+ if (isOOM) infoArr.add (F (" - out of memory" ));
2717+ else if (dmType < 254 ) infoArr.add (F (" - check pin settings" ));
26712718 }
26722719 }
26732720
0 commit comments