AudioSource improvements (work in progress)

-new methods: getType(), isInitailized(), postProcessSample()
- allow users to compile for RIGHT audio channel (-D I2S_USE_RIGHT_CHANNEL)
- better handling in case audio input driver failed to initialize
- removed some unneeded code and unneeded parameters
This commit is contained in:
Frank 2022-08-10 17:18:43 +02:00
parent 924073424f
commit 5e6532959b
2 changed files with 74 additions and 52 deletions

View File

@ -728,11 +728,11 @@ class AudioReactive : public Usermod {
} }
void receiveAudioData() bool receiveAudioData() // check & process new data. return TRUE in case that new audio data was received.
{ {
if (!udpSyncConnected) return; if (!udpSyncConnected) return false;
//DEBUGSR_PRINTLN("Checking for UDP Microphone Packet"); //DEBUGSR_PRINTLN("Checking for UDP Microphone Packet");
bool haveFreshData = false;
size_t packetSize = fftUdp.parsePacket(); size_t packetSize = fftUdp.parsePacket();
if (packetSize > 5) { if (packetSize > 5) {
//DEBUGSR_PRINTLN("Received UDP Sync Packet"); //DEBUGSR_PRINTLN("Received UDP Sync Packet");
@ -774,8 +774,10 @@ class AudioReactive : public Usermod {
FFT_Magnitude = my_magnitude; FFT_Magnitude = my_magnitude;
FFT_MajorPeak = receivedPacket->FFT_MajorPeak; FFT_MajorPeak = receivedPacket->FFT_MajorPeak;
//DEBUGSR_PRINTLN("Finished parsing UDP Sync Packet"); //DEBUGSR_PRINTLN("Finished parsing UDP Sync Packet");
haveFreshData = true;
} }
} }
return haveFreshData;
} }
@ -822,50 +824,54 @@ class AudioReactive : public Usermod {
delay(100); // Give that poor microphone some time to setup. delay(100); // Give that poor microphone some time to setup.
switch (dmType) { switch (dmType) {
case 1: case 1:
DEBUGSR_PRINTLN(F("AS: Generic I2S Microphone.")); DEBUGSR_PRINT(F("AR: Generic I2S Microphone - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT));
audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE, 0, 0xFFFFFFFF); audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE);
delay(100); delay(100);
if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin); if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin);
break; break;
case 2: case 2:
DEBUGSR_PRINTLN(F("AS: ES7243 Microphone.")); DEBUGSR_PRINTLN(F("AR: ES7243 Microphone (right channel only)."));
audioSource = new ES7243(SAMPLE_RATE, BLOCK_SIZE, 0, 0xFFFFFFFF); audioSource = new ES7243(SAMPLE_RATE, BLOCK_SIZE);
delay(100); delay(100);
if (audioSource) audioSource->initialize(sdaPin, sclPin, i2swsPin, i2ssdPin, i2sckPin, mclkPin); if (audioSource) audioSource->initialize(sdaPin, sclPin, i2swsPin, i2ssdPin, i2sckPin, mclkPin);
break; break;
case 3: case 3:
DEBUGSR_PRINTLN(F("AS: SPH0645 Microphone")); DEBUGSR_PRINT(F("AR: SPH0645 Microphone - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT));
audioSource = new SPH0654(SAMPLE_RATE, BLOCK_SIZE, 0, 0xFFFFFFFF); audioSource = new SPH0654(SAMPLE_RATE, BLOCK_SIZE);
delay(100); delay(100);
audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin); audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin);
break; break;
case 4: case 4:
DEBUGSR_PRINTLN(F("AS: Generic I2S Microphone with Master Clock")); DEBUGSR_PRINT(F("AR: Generic I2S Microphone with Master Clock - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT));
audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE, 0, 0xFFFFFFFF); audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE);
delay(100); delay(100);
if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin);
break; break;
case 5: case 5:
DEBUGSR_PRINTLN(F("AS: I2S PDM Microphone")); DEBUGSR_PRINT(F("AR: I2S PDM Microphone - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT));
audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE, 0, 0xFFFFFFFF); audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE);
delay(100); delay(100);
if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin); if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin);
break; break;
case 0: case 0:
default: default:
DEBUGSR_PRINTLN(F("AS: Analog Microphone.")); DEBUGSR_PRINTLN(F("AR: Analog Microphone (left channel only)."));
// we don't do the down-shift by 16bit any more audioSource = new I2SAdcSource(SAMPLE_RATE, BLOCK_SIZE);
//audioSource = new I2SAdcSource(SAMPLE_RATE, BLOCK_SIZE, -4, 0x0FFF); // request upscaling to 16bit - still produces too much noise
audioSource = new I2SAdcSource(SAMPLE_RATE, BLOCK_SIZE, 0, 0x0FFF); // keep at 12bit - less noise
delay(100); delay(100);
if (audioSource) audioSource->initialize(audioPin); if (audioSource) audioSource->initialize(audioPin);
break; break;
} }
delay(250); // give mictophone enough time to initialise delay(250); // give microphone enough time to initialise
if (!audioSource) enabled = false; // audio failed to initialise if (!audioSource) enabled = false; // audio failed to initialise
if (enabled) onUpdateBegin(false); // create FFT task if (enabled) onUpdateBegin(false); // create FFT task
if (enabled) disableSoundProcessing = false; if (FFT_Task == nullptr) enabled = false; // FFT task creation failed
if (enabled) disableSoundProcessing = false; // all good - enable audio processing
if((!audioSource) || (!audioSource->isInitialized())) { // audio source failed to initialize. Still stay "enabled", as there might be input arriving via UDP Sound Sync
DEBUGSR_PRINTLN(F("AR: Failed to initialize sound input driver. Please check input PIN settings."));
disableSoundProcessing = true;
}
initDone = true; initDone = true;
} }
@ -1019,7 +1025,7 @@ class AudioReactive : public Usermod {
// Begin UDP Microphone Sync // Begin UDP Microphone Sync
if ((audioSyncEnabled & 0x02) && millis() - lastTime > delayMs) { // Only run the audio listener code if we're in Receive mode if ((audioSyncEnabled & 0x02) && millis() - lastTime > delayMs) { // Only run the audio listener code if we're in Receive mode
receiveAudioData(); (void) receiveAudioData(); // ToDo: use return value for something meaningfull
lastTime = millis(); lastTime = millis();
} }

View File

@ -18,19 +18,36 @@
Until this configuration is moved to the webinterface Until this configuration is moved to the webinterface
*/ */
// if you have problems to get your microphone work on the left channel, uncomment the following line
//#define I2S_USE_RIGHT_CHANNEL // (experimental) define this to use right channel (digital mics only)
#ifdef I2S_USE_RIGHT_CHANNEL
#define I2S_MIC_CHANNEL I2S_CHANNEL_FMT_ONLY_RIGHT
#define I2S_MIC_CHANNEL_TEXT "right channel only."
#else
#define I2S_MIC_CHANNEL I2S_CHANNEL_FMT_ONLY_LEFT
#define I2S_MIC_CHANNEL_TEXT "left channel only."
#endif
// Uncomment the line below to utilize ADC1 _exclusively_ for I2S sound input.
// benefit: analog mic inputs will be sampled contiously -> better response times and less "glitches"
// WARNING: this option WILL lock-up your device in case that any other analogRead() operation is performed;
// for example if you want to read "analog buttons"
//#define I2S_GRAB_ADC1_COMPLETELY // (experimental) continously sample analog ADC microphone. WARNING will cause analogRead() lock-up
// data type requested from the I2S driver - currently we always use 32bit // data type requested from the I2S driver - currently we always use 32bit
//#define I2S_USE_16BIT_SAMPLES // (experimental) define this to request 16bit - more efficient but possibly less compatible //#define I2S_USE_16BIT_SAMPLES // (experimental) define this to request 16bit - more efficient but possibly less compatible
#ifdef I2S_USE_16BIT_SAMPLES #ifdef I2S_USE_16BIT_SAMPLES
#define I2S_SAMPLE_RESOLUTION I2S_BITS_PER_SAMPLE_16BIT #define I2S_SAMPLE_RESOLUTION I2S_BITS_PER_SAMPLE_16BIT
#define I2S_datatype int16_t #define I2S_datatype int16_t
#define I2S_unsigned_datatype uint16_t
#undef I2S_SAMPLE_DOWNSCALE_TO_16BIT #undef I2S_SAMPLE_DOWNSCALE_TO_16BIT
#else #else
#define I2S_SAMPLE_RESOLUTION I2S_BITS_PER_SAMPLE_32BIT #define I2S_SAMPLE_RESOLUTION I2S_BITS_PER_SAMPLE_32BIT
#define I2S_datatype int32_t #define I2S_datatype int32_t
#define I2S_unsigned_datatype uint32_t
#define I2S_SAMPLE_DOWNSCALE_TO_16BIT #define I2S_SAMPLE_DOWNSCALE_TO_16BIT
#endif #endif
/* Interface class /* Interface class
AudioSource serves as base class for all microphone types AudioSource serves as base class for all microphone types
This enables accessing all microphones with one single interface This enables accessing all microphones with one single interface
@ -65,15 +82,23 @@ class AudioSource {
/* Get an up-to-date sample without DC offset */ /* Get an up-to-date sample without DC offset */
virtual int getSampleWithoutDCOffset() { return _sampleNoDCOffset; }; virtual int getSampleWithoutDCOffset() { return _sampleNoDCOffset; };
/* check if the audio source driver was initialized successfully */
virtual bool isInitialized(void) {return(_initialized);}
/* identify Audiosource type - I2S-ADC or I2S-digital */
typedef enum{Type_unknown=0, Type_I2SAdc=1, Type_I2SDigital=2} AudioSourceType;
virtual AudioSourceType getType(void) {return(Type_I2SDigital);} // default is "I2S digital source" - ADC type overrides this method
protected: protected:
/* Post-process audio sample - currently on needed for I2SAdcSource*/
virtual I2S_datatype postProcessSample(I2S_datatype sample_in) {return(sample_in);} // default method can be overriden by instances (ADC) that need sample postprocessing
// Private constructor, to make sure it is not callable except from derived classes // Private constructor, to make sure it is not callable except from derived classes
AudioSource(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) : AudioSource(int sampleRate, int blockSize) :
_sampleRate(sampleRate), _sampleRate(sampleRate),
_blockSize(blockSize), _blockSize(blockSize),
_sampleNoDCOffset(0), _sampleNoDCOffset(0),
_dcOffset(0.0f), _dcOffset(0.0f),
_shift(lshift),
_mask(mask),
_initialized(false) _initialized(false)
{}; {};
@ -81,8 +106,6 @@ class AudioSource {
int _blockSize; // I2S block size int _blockSize; // I2S block size
volatile int _sampleNoDCOffset; // Up-to-date sample without DCOffset volatile int _sampleNoDCOffset; // Up-to-date sample without DCOffset
float _dcOffset; // Rolling average DC offset float _dcOffset; // Rolling average DC offset
int16_t _shift; // Shift obtained samples to the right (positive) or left(negative) by this amount
uint32_t _mask; // Bitmask for sample data after shifting. Bitmask 0X0FFF means that we need to convert 12bit ADC samples from unsigned to signed
bool _initialized; // Gets set to true if initialization is successful bool _initialized; // Gets set to true if initialization is successful
}; };
@ -91,13 +114,13 @@ class AudioSource {
*/ */
class I2SSource : public AudioSource { class I2SSource : public AudioSource {
public: public:
I2SSource(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) : I2SSource(int sampleRate, int blockSize) :
AudioSource(sampleRate, blockSize, lshift, mask) { AudioSource(sampleRate, blockSize) {
_config = { _config = {
.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX), .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX),
.sample_rate = _sampleRate, .sample_rate = _sampleRate,
.bits_per_sample = I2S_SAMPLE_RESOLUTION, .bits_per_sample = I2S_SAMPLE_RESOLUTION,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, .channel_format = I2S_MIC_CHANNEL,
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0) #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0)
.communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_STAND_I2S), .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_STAND_I2S),
#else #else
@ -193,26 +216,16 @@ class I2SSource : public AudioSource {
// Store samples in sample buffer and update DC offset // Store samples in sample buffer and update DC offset
for (int i = 0; i < num_samples; i++) { for (int i = 0; i < num_samples; i++) {
// pre-shift samples down to 16bit newSamples[i] = postProcessSample(newSamples[i]); // perform postprocessing (needed for ADC samples)
#ifdef I2S_SAMPLE_DOWNSCALE_TO_16BIT
if (_shift != 0)
newSamples[i] >>= 16;
#endif
float currSample = 0.0f; float currSample = 0.0f;
if(_shift > 0)
currSample = (float) (newSamples[i] >> _shift);
else {
if(_shift < 0)
currSample = (float) (newSamples[i] << (- _shift)); // need to "pump up" 12bit ADC to full 16bit as delivered by other digital mics
else
#ifdef I2S_SAMPLE_DOWNSCALE_TO_16BIT #ifdef I2S_SAMPLE_DOWNSCALE_TO_16BIT
currSample = (float) newSamples[i] / 65536.0f; // _shift == 0 -> use the chance to keep lower 16bits currSample = (float) newSamples[i] / 65536.0f; // 32bit input -> 16bit; keeping lower 16bits as decimal places
#else #else
currSample = (float) newSamples[i]; currSample = (float) newSamples[i]; // 16bit input -> use as-is
#endif #endif
}
buffer[i] = currSample; buffer[i] = currSample;
_dcOffset = ((_dcOffset * 31) + currSample) / 32; _dcOffset = ((_dcOffset * 31.0f) + currSample) / 32.0f;
} }
// Update no-DC sample // Update no-DC sample
@ -275,8 +288,8 @@ class ES7243 : public I2SSource {
} }
public: public:
ES7243(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) : ES7243(int sampleRate, int blockSize) :
I2SSource(sampleRate, blockSize, lshift, mask) { I2SSource(sampleRate, blockSize) {
_config.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT; _config.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT;
}; };
@ -314,8 +327,8 @@ public:
*/ */
class I2SAdcSource : public I2SSource { class I2SAdcSource : public I2SSource {
public: public:
I2SAdcSource(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) : I2SAdcSource(int sampleRate, int blockSize) :
I2SSource(sampleRate, blockSize, lshift, mask) { I2SSource(sampleRate, blockSize) {
_config = { _config = {
.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN), .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN),
.sample_rate = _sampleRate, .sample_rate = _sampleRate,
@ -332,6 +345,9 @@ class I2SAdcSource : public I2SSource {
}; };
} }
/* identify Audiosource type - I2S-ADC*/
AudioSourceType getType(void) {return(Type_I2SAdc);}
void initialize(int8_t audioPin, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) { void initialize(int8_t audioPin, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) {
if(!pinManager.allocatePin(audioPin, false, PinOwner::UM_Audioreactive)) { if(!pinManager.allocatePin(audioPin, false, PinOwner::UM_Audioreactive)) {
return; return;
@ -432,8 +448,8 @@ class I2SAdcSource : public I2SSource {
*/ */
class SPH0654 : public I2SSource { class SPH0654 : public I2SSource {
public: public:
SPH0654(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) : SPH0654(int sampleRate, int blockSize) :
I2SSource(sampleRate, blockSize, lshift, mask) I2SSource(sampleRate, blockSize)
{} {}
void initialize(uint8_t i2swsPin, uint8_t i2ssdPin, uint8_t i2sckPin, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) { void initialize(uint8_t i2swsPin, uint8_t i2ssdPin, uint8_t i2sckPin, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) {