From fc24ea881a322562677b9a133ebe0dbbe434fe4a Mon Sep 17 00:00:00 2001 From: Hendrik Langer Date: Mon, 20 Aug 2018 16:41:45 +0200 Subject: [PATCH 1/4] Support for I2S microphones like SPH0645 (fixes #70) --- .../I2SInputEchoTest/I2SInputEchoTest.ino | 40 ++++ keywords.txt | 1 + src/AudioInputI2S.cpp | 191 ++++++++++++++++++ src/AudioInputI2S.h | 46 +++++ 4 files changed, 278 insertions(+) create mode 100644 examples/I2SInputEchoTest/I2SInputEchoTest.ino create mode 100644 src/AudioInputI2S.cpp create mode 100644 src/AudioInputI2S.h diff --git a/examples/I2SInputEchoTest/I2SInputEchoTest.ino b/examples/I2SInputEchoTest/I2SInputEchoTest.ino new file mode 100644 index 00000000..fabe4eff --- /dev/null +++ b/examples/I2SInputEchoTest/I2SInputEchoTest.ino @@ -0,0 +1,40 @@ +#include + +#include +#ifdef ESP32 + #include +#else + #include +#endif + +#include "AudioInputI2S.h" +#include "AudioOutputI2S.h" +#include "AudioOutputBuffer.h" + +AudioInputI2S *in; +AudioOutputI2S *out; +AudioOutputBuffer *outbuf; + +void setup() +{ + WiFi.mode(WIFI_OFF); + Serial.begin(115200); + out = new AudioOutputI2S(0); + in = new AudioInputI2S(1); + out->SetPinout(26, 25, 22); + in->SetPinout(23, 18, 5); + //out->SetGain(0.02); + //in->SetGain(0.1); + + outbuf = new AudioOutputBuffer(0.7*44100, out); + in->begin(outbuf); + Serial.printf("starting\n"); +} + +void loop() +{ + if (in->isRunning()) { + if (!in->loop()) in->stop(); + } + delay(5); +} diff --git a/keywords.txt b/keywords.txt index 560cdfc5..334e81da 100644 --- a/keywords.txt +++ b/keywords.txt @@ -25,3 +25,4 @@ AudioOutputSerialWAV KEYWORD1 AudioOutputSPIFFSWAV KEYWORD1 AudioOutputMixer KEYWORD1 AudioOutputMixerStub KEYWORD1 +AudioInputI2S KEYWORD1 diff --git a/src/AudioInputI2S.cpp b/src/AudioInputI2S.cpp new file mode 100644 index 00000000..e09aca1a --- /dev/null +++ b/src/AudioInputI2S.cpp @@ -0,0 +1,191 @@ +/* + AudioInputI2S + I2S audio source + +*/ + +#include +#ifdef ESP32 + #include "driver/i2s.h" +#else + #include +#endif +#include "AudioInputI2S.h" + +AudioInputI2S::AudioInputI2S(int port, int dma_buf_count, int use_apll) +{ + this->portNo = port; + this->i2sOn = false; + this->hertz = 44100; + + buffLen = dma_buf_count*64; + buff = (uint8_t*)malloc(buffLen); + +#ifdef ESP32 + if (!i2sOn) { + if (use_apll == APLL_AUTO) { + // don't use audio pll on buggy rev0 chips + use_apll = APLL_DISABLE; + esp_chip_info_t out_info; + esp_chip_info(&out_info); + if(out_info.revision > 0) { + use_apll = APLL_ENABLE; + } + } + + i2s_config_t i2s_config_adc = { + .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX), + .sample_rate = hertz, + .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT, + .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT, + .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S), + .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // high interrupt priority + .dma_buf_count = dma_buf_count, + .dma_buf_len = 64, + .use_apll = use_apll // Use audio PLL + }; + Serial.printf("+%d %p\n", portNo, &i2s_config_adc); + if (i2s_driver_install((i2s_port_t)portNo, &i2s_config_adc, 0, NULL) != ESP_OK) { + Serial.println("ERROR: Unable to install I2S drives\n"); + } + //SetPinout(21, 25, 19); // be careful not to overwrite the other i2s channel + + i2s_zero_dma_buffer((i2s_port_t)portNo); + } +#else + (void) use_apll; + if (!i2sOn) { + i2s_begin(); + } +#endif + i2sOn = true; + bps = 32; + channels = 1; + gain_shift = 0; +} + +AudioInputI2S::~AudioInputI2S() +{ +#ifdef ESP32 + if (i2sOn) { + Serial.printf("UNINSTALL I2S\n"); + i2s_driver_uninstall((i2s_port_t)portNo); //stop & destroy i2s driver + } +#else + if (i2sOn) i2s_end(); +#endif +i2sOn = false; +} + +uint32_t AudioInputI2S::GetSample() +{ + if (!i2sOn) return 0; + + uint32_t sampleIn=0; + i2s_pop_sample((i2s_port_t)portNo, (char*)&sampleIn, portMAX_DELAY); + + return sampleIn>>(14+gain_shift); +} + +uint32_t AudioInputI2S::read(void* data, size_t len_bytes) +{ + if (!i2sOn) return 0; + size_t bytes_read = 0; + //bytes_read = i2s_read_bytes((i2s_port_t)portNo, (char*)data, (size_t)len, portMAX_DELAY); + esp_err_t err = i2s_read((i2s_port_t)portNo, data, len_bytes, &bytes_read, 0); + return bytes_read; +} + +bool AudioInputI2S::SetPinout(int bclk, int wclk, int din) +{ +#ifdef ESP32 + i2s_pin_config_t pin_config = { + .bck_io_num = bclk, + .ws_io_num = wclk, + .data_out_num = I2S_PIN_NO_CHANGE, + .data_in_num = din + }; + i2s_set_pin((i2s_port_t)portNo, &pin_config); + return true; +#else + (void) bclk; + (void) wclk; + (void) din; + return false; +#endif +} + +bool AudioInputI2S::SetRate(int hz) +{ + // TODO - have a list of allowable rates from constructor, check them + this->hertz = hz; +#ifdef ESP32 + i2s_set_sample_rates((i2s_port_t)portNo, hz); +#else + i2s_set_rate(hz); +#endif + return true; +} + +bool AudioInputI2S::SetBitsPerSample(int bits) +{ + if ( (bits != 32) && (bits != 16) && (bits != 8) ) return false; + this->bps = bits; + return true; +} + +bool AudioInputI2S::SetGain(float f) +{ + if (f < 0.33) this->gain_shift = 2; + else if (f < 0.66) this->gain_shift = 1; + else this->gain_shift = 0; + return true; +} + +bool AudioInputI2S::stop() { + i2sOn = false; + output->stop(); + esp_err_t err = i2s_stop((i2s_port_t)portNo); + return (err == ESP_OK); +} + +bool AudioInputI2S::isRunning() { + return i2sOn; +} + +bool AudioInputI2S::loop() { + if (!i2sOn) return false; + + uint32_t* buff32 = reinterpret_cast(buff); + + while (validSamples) { + int32_t sample = buff32[curSample]>>(14+gain_shift); + int16_t lastSample[2] = {sample, sample}; + if (!output->ConsumeSample(lastSample)) { + output->loop(); + yield(); + return true; + } + validSamples--; + curSample++; + } + + validSamples = read(buff, buffLen) / sizeof(uint32_t); + curSample = 0; + + output->loop(); +} + +bool AudioInputI2S::begin(AudioOutput *output) { + if (!output) return false; + if (!i2sOn) { + esp_err_t err = i2s_start((i2s_port_t)portNo); + if (err != ESP_OK) return false; + i2sOn = true; + } + this->output = output; + output->begin(); + output->SetBitsPerSample(bps); + output->SetRate(hertz); + return true; +} diff --git a/src/AudioInputI2S.h b/src/AudioInputI2S.h new file mode 100644 index 00000000..5a0a1f2a --- /dev/null +++ b/src/AudioInputI2S.h @@ -0,0 +1,46 @@ +/* + AudioInputI2S + I2S audio source + +*/ + +#ifndef _AUDIOINPUTI2S_H +#define _AUDIOINPUTI2S_H + +#include +#include "AudioStatus.h" +#include "AudioOutput.h" + +class AudioInputI2S +{ + public: + AudioInputI2S(int port=0, int dma_buf_count = 8, int use_apll=APLL_DISABLE); + virtual ~AudioInputI2S(); + bool SetPinout(int bclkPin, int wclkPin, int dinPin); + virtual bool begin(AudioOutput *output); + virtual bool loop(); + virtual bool stop(); + virtual bool isRunning(); + virtual bool SetRate(int hz); + virtual bool SetBitsPerSample(int bits); + virtual bool SetGain(float f); + uint32_t GetSample(void); + virtual uint32_t read(void* data, size_t len_bytes); + + enum : int { APLL_AUTO = -1, APLL_ENABLE = 1, APLL_DISABLE = 0 }; + private: + protected: + uint8_t portNo; + bool i2sOn; + uint16_t hertz; + uint8_t bps; + uint8_t channels; + AudioOutput *output; + int16_t buffLen; + uint8_t *buff; + int16_t validSamples; + int16_t curSample; + int gain_shift; +}; + +#endif // _AUDIOINPUTI2S_H From 1ec1d4518c2f85a3228db0626584a6f740865da8 Mon Sep 17 00:00:00 2001 From: Hendrik Langer Date: Mon, 20 Aug 2018 17:17:54 +0200 Subject: [PATCH 2/4] make this an AudioGenerator --- .../I2SInputEchoTest/I2SInputEchoTest.ino | 2 +- src/AudioInputI2S.cpp | 33 ++++++++++--------- src/AudioInputI2S.h | 16 ++++----- 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/examples/I2SInputEchoTest/I2SInputEchoTest.ino b/examples/I2SInputEchoTest/I2SInputEchoTest.ino index fabe4eff..68c9da5c 100644 --- a/examples/I2SInputEchoTest/I2SInputEchoTest.ino +++ b/examples/I2SInputEchoTest/I2SInputEchoTest.ino @@ -27,7 +27,7 @@ void setup() //in->SetGain(0.1); outbuf = new AudioOutputBuffer(0.7*44100, out); - in->begin(outbuf); + in->begin(NULL, outbuf); Serial.printf("starting\n"); } diff --git a/src/AudioInputI2S.cpp b/src/AudioInputI2S.cpp index e09aca1a..4cbb5b67 100644 --- a/src/AudioInputI2S.cpp +++ b/src/AudioInputI2S.cpp @@ -15,14 +15,14 @@ AudioInputI2S::AudioInputI2S(int port, int dma_buf_count, int use_apll) { this->portNo = port; - this->i2sOn = false; + this->running = false; this->hertz = 44100; buffLen = dma_buf_count*64; buff = (uint8_t*)malloc(buffLen); #ifdef ESP32 - if (!i2sOn) { + if (!running) { if (use_apll == APLL_AUTO) { // don't use audio pll on buggy rev0 chips use_apll = APLL_DISABLE; @@ -54,11 +54,11 @@ AudioInputI2S::AudioInputI2S(int port, int dma_buf_count, int use_apll) } #else (void) use_apll; - if (!i2sOn) { + if (!running) { i2s_begin(); } #endif - i2sOn = true; + running = true; bps = 32; channels = 1; gain_shift = 0; @@ -67,19 +67,19 @@ AudioInputI2S::AudioInputI2S(int port, int dma_buf_count, int use_apll) AudioInputI2S::~AudioInputI2S() { #ifdef ESP32 - if (i2sOn) { + if (running) { Serial.printf("UNINSTALL I2S\n"); i2s_driver_uninstall((i2s_port_t)portNo); //stop & destroy i2s driver } #else - if (i2sOn) i2s_end(); + if (running) i2s_end(); #endif -i2sOn = false; +running = false; } uint32_t AudioInputI2S::GetSample() { - if (!i2sOn) return 0; + if (!running) return 0; uint32_t sampleIn=0; i2s_pop_sample((i2s_port_t)portNo, (char*)&sampleIn, portMAX_DELAY); @@ -89,7 +89,7 @@ uint32_t AudioInputI2S::GetSample() uint32_t AudioInputI2S::read(void* data, size_t len_bytes) { - if (!i2sOn) return 0; + if (!running) return 0; size_t bytes_read = 0; //bytes_read = i2s_read_bytes((i2s_port_t)portNo, (char*)data, (size_t)len, portMAX_DELAY); esp_err_t err = i2s_read((i2s_port_t)portNo, data, len_bytes, &bytes_read, 0); @@ -143,24 +143,25 @@ bool AudioInputI2S::SetGain(float f) } bool AudioInputI2S::stop() { - i2sOn = false; + running = false; output->stop(); esp_err_t err = i2s_stop((i2s_port_t)portNo); return (err == ESP_OK); } bool AudioInputI2S::isRunning() { - return i2sOn; + return running; } bool AudioInputI2S::loop() { - if (!i2sOn) return false; + if (!running) return false; uint32_t* buff32 = reinterpret_cast(buff); while (validSamples) { int32_t sample = buff32[curSample]>>(14+gain_shift); - int16_t lastSample[2] = {sample, sample}; + lastSample[0] = sample; + lastSample[1] = sample; if (!output->ConsumeSample(lastSample)) { output->loop(); yield(); @@ -176,12 +177,12 @@ bool AudioInputI2S::loop() { output->loop(); } -bool AudioInputI2S::begin(AudioOutput *output) { +bool AudioInputI2S::begin(AudioFileSource *source, AudioOutput *output) { if (!output) return false; - if (!i2sOn) { + if (!running) { esp_err_t err = i2s_start((i2s_port_t)portNo); if (err != ESP_OK) return false; - i2sOn = true; + running = true; } this->output = output; output->begin(); diff --git a/src/AudioInputI2S.h b/src/AudioInputI2S.h index 5a0a1f2a..701956ba 100644 --- a/src/AudioInputI2S.h +++ b/src/AudioInputI2S.h @@ -8,19 +8,18 @@ #define _AUDIOINPUTI2S_H #include -#include "AudioStatus.h" -#include "AudioOutput.h" +#include "AudioGenerator.h" -class AudioInputI2S +class AudioInputI2S : public AudioGenerator { public: AudioInputI2S(int port=0, int dma_buf_count = 8, int use_apll=APLL_DISABLE); - virtual ~AudioInputI2S(); + virtual ~AudioInputI2S() override; bool SetPinout(int bclkPin, int wclkPin, int dinPin); - virtual bool begin(AudioOutput *output); - virtual bool loop(); - virtual bool stop(); - virtual bool isRunning(); + virtual bool begin(AudioFileSource *source, AudioOutput *output) override; + virtual bool loop() override; + virtual bool stop() override; + virtual bool isRunning() override; virtual bool SetRate(int hz); virtual bool SetBitsPerSample(int bits); virtual bool SetGain(float f); @@ -31,7 +30,6 @@ class AudioInputI2S private: protected: uint8_t portNo; - bool i2sOn; uint16_t hertz; uint8_t bps; uint8_t channels; From 056077356f30d7e7b09259bf816f776fe338fa41 Mon Sep 17 00:00:00 2001 From: Hendrik Langer Date: Mon, 20 Aug 2018 18:20:04 +0200 Subject: [PATCH 3/4] make this compile on esp8266 (thanks CI) Try to imlpement all the platform specific functions correctly for esp8266. --- .../I2SInputEchoTest/I2SInputEchoTest.ino | 1 - src/AudioInputI2S.cpp | 24 ++++++++++++++++++- src/AudioInputI2S.h | 1 + 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/examples/I2SInputEchoTest/I2SInputEchoTest.ino b/examples/I2SInputEchoTest/I2SInputEchoTest.ino index 68c9da5c..89f58226 100644 --- a/examples/I2SInputEchoTest/I2SInputEchoTest.ino +++ b/examples/I2SInputEchoTest/I2SInputEchoTest.ino @@ -1,6 +1,5 @@ #include -#include #ifdef ESP32 #include #else diff --git a/src/AudioInputI2S.cpp b/src/AudioInputI2S.cpp index 4cbb5b67..f01e3deb 100644 --- a/src/AudioInputI2S.cpp +++ b/src/AudioInputI2S.cpp @@ -82,9 +82,13 @@ uint32_t AudioInputI2S::GetSample() if (!running) return 0; uint32_t sampleIn=0; +#ifdef ESP32 i2s_pop_sample((i2s_port_t)portNo, (char*)&sampleIn, portMAX_DELAY); - return sampleIn>>(14+gain_shift); +#else + i2s_read_sample(&lastSample[LEFTCHANNEL], &lastSample[RIGHTCHANNEL], true); + return (lastSample[RIGHTCHANNEL] << 16) | (lastSample[LEFTCHANNEL] & 0xffff); +#endif } uint32_t AudioInputI2S::read(void* data, size_t len_bytes) @@ -92,7 +96,16 @@ uint32_t AudioInputI2S::read(void* data, size_t len_bytes) if (!running) return 0; size_t bytes_read = 0; //bytes_read = i2s_read_bytes((i2s_port_t)portNo, (char*)data, (size_t)len, portMAX_DELAY); +#ifdef ESP32 esp_err_t err = i2s_read((i2s_port_t)portNo, data, len_bytes, &bytes_read, 0); +#else + uint16_t* data16 = reinterpret_cast(data); + for (int i = 0; (i < len_bytes/(2*sizeof(int16_t))) && (i2s_rx_available()) > 0; i++) { + i2s_read_sample(&lastSample[LEFTCHANNEL], &lastSample[RIGHTCHANNEL], false); + data16[i+RIGHTCHANNEL] = lastSample[RIGHTCHANNEL]; + data16[i+LEFTCHANNEL] = lastSample[LEFTCHANNEL]; + } +#endif return bytes_read; } @@ -145,8 +158,13 @@ bool AudioInputI2S::SetGain(float f) bool AudioInputI2S::stop() { running = false; output->stop(); +#ifdef ESP32 esp_err_t err = i2s_stop((i2s_port_t)portNo); return (err == ESP_OK); +#else + i2s_rxtx_begin(false, false); + return true; +#endif } bool AudioInputI2S::isRunning() { @@ -180,8 +198,12 @@ bool AudioInputI2S::loop() { bool AudioInputI2S::begin(AudioFileSource *source, AudioOutput *output) { if (!output) return false; if (!running) { +#ifdef ESP32 esp_err_t err = i2s_start((i2s_port_t)portNo); if (err != ESP_OK) return false; +#else + i2s_rxtx_begin(true, false); +#endif running = true; } this->output = output; diff --git a/src/AudioInputI2S.h b/src/AudioInputI2S.h index 701956ba..32e240ab 100644 --- a/src/AudioInputI2S.h +++ b/src/AudioInputI2S.h @@ -26,6 +26,7 @@ class AudioInputI2S : public AudioGenerator uint32_t GetSample(void); virtual uint32_t read(void* data, size_t len_bytes); + enum : int { LEFTCHANNEL=0, RIGHTCHANNEL=1 }; enum : int { APLL_AUTO = -1, APLL_ENABLE = 1, APLL_DISABLE = 0 }; private: protected: From 55b96ad1cd4c119a2204950d5decacfec97b62b9 Mon Sep 17 00:00:00 2001 From: h3ndrik Date: Mon, 20 Aug 2018 22:03:37 +0200 Subject: [PATCH 4/4] free memory --- src/AudioInputI2S.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/AudioInputI2S.cpp b/src/AudioInputI2S.cpp index f01e3deb..fef0da59 100644 --- a/src/AudioInputI2S.cpp +++ b/src/AudioInputI2S.cpp @@ -75,6 +75,7 @@ AudioInputI2S::~AudioInputI2S() if (running) i2s_end(); #endif running = false; +free(buff); } uint32_t AudioInputI2S::GetSample()