From 7d1b6f63b53d5253e54bba6119a1f1e8924d5654 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 1 Dec 2022 17:47:04 +0100 Subject: [PATCH] Definition cleanup and AudioModule WIP --- arch/esp32/esp32s3.ini | 2 +- src/mesh/RadioInterface.h | 1 - src/mesh/RadioLibInterface.h | 3 +- src/modules/esp32/AudioModule.cpp | 102 ++++++++++++++++++++---------- src/modules/esp32/AudioModule.h | 16 +++-- src/platform/portduino/SimRadio.h | 1 - 6 files changed, 81 insertions(+), 44 deletions(-) diff --git a/arch/esp32/esp32s3.ini b/arch/esp32/esp32s3.ini index 72b176999..b276ceff9 100644 --- a/arch/esp32/esp32s3.ini +++ b/arch/esp32/esp32s3.ini @@ -35,7 +35,7 @@ lib_deps = https://github.com/meshtastic/esp32_https_server.git#23665b3adc080a311dcbb586ed5941b5f94d6ea2 h2zero/NimBLE-Arduino@^1.4.0 https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6 - caveman99/ESP32 Codec2@^1.0.1 + caveman99/ESP32 Codec2@^1.0.1 lib_ignore = segger_rtt diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 437f294a4..f50c0ae77 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -1,6 +1,5 @@ #pragma once -#include "../concurrency/NotifiedWorkerThread.h" #include "MemoryPool.h" #include "MeshTypes.h" #include "Observer.h" diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index f368cf83e..16495c2f4 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -1,10 +1,9 @@ #pragma once -#include "../concurrency/OSThread.h" +#include "concurrency/NotifiedWorkerThread.h" #include "RadioInterface.h" #include "MeshPacketQueue.h" -#define RADIOLIB_EXCLUDE_HTTP #include // ESP32 has special rules about ISR code diff --git a/src/modules/esp32/AudioModule.cpp b/src/modules/esp32/AudioModule.cpp index 55677c0a9..7ff46bf10 100644 --- a/src/modules/esp32/AudioModule.cpp +++ b/src/modules/esp32/AudioModule.cpp @@ -42,6 +42,13 @@ #define AAMP 14 #define PTT_PIN 39 +#ifdef ARCH_ESP32 +// ESP32 doesn't use that flag +#define YIELD_FROM_ISR(x) portYIELD_FROM_ISR() +#else +#define YIELD_FROM_ISR(x) portYIELD_FROM_ISR(x) +#endif + // #define I2S_WS 13 // #define I2S_SD 15 // #define I2S_SIN 2 @@ -51,7 +58,6 @@ #define I2S_PORT I2S_NUM_0 #define AUDIO_MODULE_RX_BUFFER 128 -#define AUDIO_MODULE_DATA_MAX Constants_DATA_PAYLOAD_LEN #define AUDIO_MODULE_MODE ModuleConfig_AudioConfig_Audio_Baud_CODEC2_700 AudioModule *audioModule; @@ -107,11 +113,11 @@ IRAM_ATTR void am_onTimer() if (adc_buffer_index == ADC_BUFFER_SIZE) { adc_buffer_index = 0; - DEBUG_MSG("--- memcpy\n"); + DEBUG_MSG("♪♫♪ memcpy\n"); memcpy((void*)speech, (void*)adc_buffer, 2 * ADC_BUFFER_SIZE); // Notify codec2 task that the buffer is ready. BaseType_t xHigherPriorityTaskWoken = pdFALSE; - DEBUG_MSG("--- notifyFromISR\n"); + DEBUG_MSG("♪♫♪ notifyFromISR\n"); codec2Thread->notifyFromISR(&xHigherPriorityTaskWoken, RadioState::tx, true); if (xHigherPriorityTaskWoken) portYIELD_FROM_ISR(); @@ -133,42 +139,51 @@ IRAM_ATTR void am_onTimer() Codec2Thread::Codec2Thread() : concurrency::NotifiedWorkerThread("Codec2Thread") { if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) { - DEBUG_MSG("--- Setting up codec2 in mode %u\n", moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE); - codec2_state = codec2_create(moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE); + DEBUG_MSG("♪♫♪ Setting up codec2 in mode %u", (moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1); + codec2_state = codec2_create((moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1); codec2_set_lpc_post_filter(codec2_state, 1, 0, 0.8, 0.2); + encode_codec_size = (codec2_bits_per_frame(codec2_state) + 7) / 8; + encode_frame_num = Constants_DATA_PAYLOAD_LEN / encode_codec_size; + encode_frame_size = encode_frame_num * encode_codec_size; // max 237 bytes + DEBUG_MSG(" using %d frames of %d bytes for a total payload length of %d bytes\n", encode_frame_num, encode_codec_size, encode_frame_size); } else { - DEBUG_MSG("--- Codec2 disabled\n"); + DEBUG_MSG("♪♫♪ Codec2 disabled (AudioModule %d, Region %s, permitted %d)\n", moduleConfig.audio.codec2_enabled, myRegion->name, myRegion->audioPermitted); } } AudioModule::AudioModule() : SinglePortModule("AudioModule", PortNum_AUDIO_APP), concurrency::OSThread("AudioModule") { audio_fifo.init(); new Codec2Thread(); + //debug + moduleConfig.audio.i2s_ws = 13; + moduleConfig.audio.i2s_sd = 15; + moduleConfig.audio.i2s_din = 2; + moduleConfig.audio.i2s_sck = 14; } -void Codec2Thread::onNotify(uint32_t notification) +void IRAM_ATTR Codec2Thread::onNotify(uint32_t notification) { switch (notification) { case RadioState::tx: for (int i = 0; i < ADC_BUFFER_SIZE; i++) speech[i] = (int16_t)hp_filter.Update((float)speech[i]); - codec2_encode(codec2_state, audioModule->tx_encode_frame + audioModule->tx_encode_frame_index, speech); + codec2_encode(codec2_state, audioModule->tx_encode_frame + tx_encode_frame_index, speech); //increment the pointer where the encoded frame must be saved - audioModule->tx_encode_frame_index += 8; + tx_encode_frame_index += encode_codec_size; - //If it is the 5th time then we have a ready trasnmission frame - if (audioModule->tx_encode_frame_index == ENCODE_FRAME_SIZE) + //If it this is reached we have a ready trasnmission frame + if (tx_encode_frame_index == encode_frame_size) { - audioModule->tx_encode_frame_index = 0; + tx_encode_frame_index = 0; //Transmit it audioModule->sendPayload(); } break; case RadioState::rx: //Make a cycle to get each codec2 frame from the received frame - for (int i = 0; i < ENCODE_FRAME_SIZE; i += ENCODE_CODEC2_SIZE) + for (int i = 0; i < encode_frame_size; i += encode_codec_size) { //Decode the codec2 frame codec2_decode(codec2_state, output_buffer, audioModule->rx_encode_frame + i); @@ -187,25 +202,28 @@ void Codec2Thread::onNotify(uint32_t notification) int32_t AudioModule::runOnce() { if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) { + esp_err_t res; if (firstTime) { // if we have I2S_SD defined, take samples from digital mic. I2S_DIN means digital output to amp. if (moduleConfig.audio.i2s_sd || moduleConfig.audio.i2s_din) { - // Set up I2S Processor configuration. This will produce 16bit samples instead of 12 from the ADC - DEBUG_MSG("--- Initializing I2S for input\n"); + // Set up I2S Processor configuration. This will produce 16bit samples at 8 kHz instead of 12 from the ADC + DEBUG_MSG("♪♫♪ Initializing I2S SD: %d DIN: %d WS: %d SCK:%d\n", moduleConfig.audio.i2s_sd, moduleConfig.audio.i2s_din, moduleConfig.audio.i2s_ws, moduleConfig.audio.i2s_sck); i2s_config_t i2s_config = { .mode = (i2s_mode_t)(I2S_MODE_MASTER | (moduleConfig.audio.i2s_sd ? I2S_MODE_RX : 0) | (moduleConfig.audio.i2s_din ? I2S_MODE_TX : 0)), .sample_rate = 8000, .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, - .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, + .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S), - .intr_alloc_flags = 0, + .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, .dma_buf_count = 8, - .dma_buf_len = ADC_BUFFER_SIZE, + .dma_buf_len = ADC_BUFFER_SIZE, // 320 * 2 bytes .use_apll = false, .tx_desc_auto_clear = true, .fixed_mclk = 0 }; - i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL); + res = i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL); + if(res != ESP_OK) + DEBUG_MSG("♪♫♪ Failed to install I2S driver: %d\n", res); const i2s_pin_config_t pin_config = { .bck_io_num = moduleConfig.audio.i2s_sck, @@ -213,13 +231,18 @@ int32_t AudioModule::runOnce() .data_out_num = moduleConfig.audio.i2s_din ? moduleConfig.audio.i2s_din : I2S_PIN_NO_CHANGE, .data_in_num = moduleConfig.audio.i2s_sd ? moduleConfig.audio.i2s_sd : I2S_PIN_NO_CHANGE }; - i2s_set_pin(I2S_PORT, &pin_config); + res = i2s_set_pin(I2S_PORT, &pin_config); + if(res != ESP_OK) + DEBUG_MSG("♪♫♪ Failed to set I2S pin config: %d\n", res); - i2s_start(I2S_PORT); + res = i2s_start(I2S_PORT); + if(res != ESP_OK) + DEBUG_MSG("♪♫♪ Failed to start I2S: %d\n", res); } if (!moduleConfig.audio.i2s_sd) { - DEBUG_MSG("--- Initializing ADC on Channel %u\n", moduleConfig.audio.mic_chan ? moduleConfig.audio.mic_chan : AMIC); + // Set up ADC if we don't have a digital microphone. + DEBUG_MSG("♪♫♪ Initializing ADC on Channel %u\n", moduleConfig.audio.mic_chan ? moduleConfig.audio.mic_chan : AMIC); mic_chan = moduleConfig.audio.mic_chan ? (adc1_channel_t)(int)moduleConfig.audio.mic_chan : (adc1_channel_t)AMIC; adc1_config_width(ADC_WIDTH_12Bit); adc1_config_channel_atten(mic_chan, ADC_ATTEN_DB_6); @@ -246,7 +269,7 @@ int32_t AudioModule::runOnce() adcTimer = timerBegin(3, 500, true); // 80 MHz / 500 = 160KHz break; } - DEBUG_MSG("--- Timer CPU Frequency: %u MHz\n", cpufreq); + DEBUG_MSG("♪♫♪ Timer CPU Frequency: %u MHz\n", cpufreq); timerAttachInterrupt(adcTimer, &am_onTimer, false); timerAlarmWrite(adcTimer, 20, true); // Interrupts when counter == 20, 8.000 times a second timerAlarmEnable(adcTimer); @@ -255,10 +278,10 @@ int32_t AudioModule::runOnce() // setup analogue DAC only if we don't use I2S for output. This is not available on ESP32-S3 #if !defined(CONFIG_IDF_TARGET_ESP32S3) if (moduleConfig.audio.i2s_din) - DEBUG_MSG("--- Initializing DAC on Pin %u\n", moduleConfig.audio.amp_pin ? moduleConfig.audio.amp_pin : AAMP); + DEBUG_MSG("♪♫♪ Initializing DAC on Pin %u\n", moduleConfig.audio.amp_pin ? moduleConfig.audio.amp_pin : AAMP); #endif // Configure PTT input - DEBUG_MSG("--- Initializing PTT on Pin %u\n", moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN); + DEBUG_MSG("♪♫♪ Initializing PTT on Pin %u\n", moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN); pinMode(moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN, INPUT); firstTime = false; @@ -266,20 +289,35 @@ int32_t AudioModule::runOnce() // Check if PTT is pressed. TODO hook that into Onebutton/Interrupt drive. if (digitalRead(moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN) == HIGH) { if (radio_state == RadioState::rx) { - DEBUG_MSG("--- PTT pressed, switching to TX\n"); + DEBUG_MSG("♪♫♪ PTT pressed, switching to TX\n"); radio_state = RadioState::tx; } } else { if (radio_state == RadioState::tx) { - DEBUG_MSG("--- PTT released, switching to RX\n"); + DEBUG_MSG("♪♫♪ PTT released, switching to RX\n"); radio_state = RadioState::rx; } } - + if ((radio_state == RadioState::tx) && moduleConfig.audio.i2s_sd) { + // Get I2S data from the microphone and place in data buffer + size_t bytesIn = 0; + res = i2s_read(I2S_PORT, &adc_buffer + adc_buffer_index, ADC_BUFFER_SIZE - adc_buffer_index, &bytesIn, pdMS_TO_TICKS(40)); // wait 40ms for audio to arrive. + + if (res == ESP_OK) { + adc_buffer_index += bytesIn; + if (adc_buffer_index == ADC_BUFFER_SIZE) { + adc_buffer_index = 0; + DEBUG_MSG("♪♫♪ We have a full buffer, process it\n"); + memcpy((void*)speech, (void*)adc_buffer, 2 * ADC_BUFFER_SIZE); + // Notify codec2 task that the buffer is ready. + codec2Thread->notify(RadioState::tx, true); + } + } + } } return 100; } else { - DEBUG_MSG("--- Audio Module Disabled\n"); + DEBUG_MSG("♪♫♪ Audio Module Disabled\n"); return INT32_MAX; } @@ -300,7 +338,7 @@ void AudioModule::sendPayload(NodeNum dest, bool wantReplies) p->want_ack = false; // Audio is shoot&forget. TODO: Is this really suppressing retransmissions? p->priority = MeshPacket_Priority_MAX; // Audio is important, because realtime - p->decoded.payload.size = ENCODE_FRAME_SIZE; + p->decoded.payload.size = codec2Thread->get_encode_frame_size(); memcpy(p->decoded.payload.bytes, tx_encode_frame, p->decoded.payload.size); service.sendToMesh(p); @@ -311,7 +349,7 @@ ProcessMessage AudioModule::handleReceived(const MeshPacket &mp) if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) { auto &p = mp.decoded; if (getFrom(&mp) != nodeDB.getNodeNum()) { - if (p.payload.size == ENCODE_FRAME_SIZE) { + if (p.payload.size == codec2Thread->get_encode_frame_size()) { memcpy(rx_encode_frame, p.payload.bytes, p.payload.size); radio_state = RadioState::rx; BaseType_t xHigherPriorityTaskWoken = pdFALSE; @@ -319,7 +357,7 @@ ProcessMessage AudioModule::handleReceived(const MeshPacket &mp) if (xHigherPriorityTaskWoken) portYIELD_FROM_ISR(); } else { - DEBUG_MSG("--- Invalid payload size %u != %u\n", p.payload.size, ENCODE_FRAME_SIZE); + DEBUG_MSG("♪♫♪ Invalid payload size %u != %u\n", p.payload.size, codec2Thread->get_encode_frame_size()); } } } diff --git a/src/modules/esp32/AudioModule.h b/src/modules/esp32/AudioModule.h index b30e82bc2..47eba55ee 100644 --- a/src/modules/esp32/AudioModule.h +++ b/src/modules/esp32/AudioModule.h @@ -1,7 +1,7 @@ #pragma once #include "SinglePortModule.h" -#include "concurrency/OSThread.h" +#include "concurrency/NotifiedWorkerThread.h" #include "configuration.h" #if defined(ARCH_ESP32) #include "NodeDB.h" @@ -14,8 +14,6 @@ #include #define ADC_BUFFER_SIZE 320 // 40ms of voice in 8KHz sampling frequency -#define ENCODE_CODEC2_SIZE 8 -#define ENCODE_FRAME_SIZE (ENCODE_CODEC2_SIZE * 5) // 5 codec2 frames of 8 bytes each class Codec2Thread : public concurrency::NotifiedWorkerThread { @@ -25,7 +23,13 @@ class Codec2Thread : public concurrency::NotifiedWorkerThread public: Codec2Thread(); + int get_encode_frame_size() { return encode_frame_size; }; + protected: + int tx_encode_frame_index = 0; + int encode_codec_size = 0; + int encode_frame_num = 0; + int encode_frame_size = 0; virtual void onNotify(uint32_t notification) override; }; @@ -35,11 +39,9 @@ class AudioModule : public SinglePortModule, private concurrency::OSThread hw_timer_t* adcTimer = NULL; uint16_t adc_buffer_index = 0; - public: - unsigned char rx_encode_frame[ENCODE_FRAME_SIZE] = {}; - unsigned char tx_encode_frame[ENCODE_FRAME_SIZE] = {}; - int tx_encode_frame_index = 0; + unsigned char rx_encode_frame[Constants_DATA_PAYLOAD_LEN] = {}; + unsigned char tx_encode_frame[Constants_DATA_PAYLOAD_LEN] = {}; AudioModule(); diff --git a/src/platform/portduino/SimRadio.h b/src/platform/portduino/SimRadio.h index dad419c62..a71cf22f8 100644 --- a/src/platform/portduino/SimRadio.h +++ b/src/platform/portduino/SimRadio.h @@ -4,7 +4,6 @@ #include "MeshPacketQueue.h" #include "wifi/WiFiServerAPI.h" -#define RADIOLIB_EXCLUDE_HTTP #include class SimRadio : public RadioInterface