From f5120a29ec2d24a6ab30dbd82065bc77e41e75f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 29 Nov 2022 11:22:18 +0100 Subject: [PATCH] WIP: audio module still does not work, but enabled for all regions where audio is permitted. # Conflicts: # variants/tlora_v2_1_18/platformio.ini --- arch/esp32/esp32.ini | 3 +- arch/esp32/esp32s3.ini | 1 + src/modules/Modules.cpp | 4 - src/modules/esp32/AudioModule.cpp | 221 +++++++++++++++++------------- src/modules/esp32/AudioModule.h | 59 +++++--- 5 files changed, 167 insertions(+), 121 deletions(-) diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini index 730b78942..70654e8ec 100644 --- a/arch/esp32/esp32.ini +++ b/arch/esp32/esp32.ini @@ -33,7 +33,8 @@ lib_deps = ${environmental_base.lib_deps} https://github.com/meshtastic/esp32_https_server.git#23665b3adc080a311dcbb586ed5941b5f94d6ea2 h2zero/NimBLE-Arduino@^1.4.0 - https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6 + https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6 + caveman99/ESP32 Codec2@^1.0.1 lib_ignore = segger_rtt diff --git a/arch/esp32/esp32s3.ini b/arch/esp32/esp32s3.ini index 0c2d7d8f1..b05772344 100644 --- a/arch/esp32/esp32s3.ini +++ b/arch/esp32/esp32s3.ini @@ -34,6 +34,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 lib_ignore = segger_rtt diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index d24fa8f26..b97e965e1 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -19,10 +19,8 @@ #ifdef ARCH_ESP32 #include "modules/esp32/RangeTestModule.h" #include "modules/esp32/StoreForwardModule.h" -#ifdef USE_SX1280 #include "modules/esp32/AudioModule.h" #endif -#endif #if defined(ARCH_ESP32) || defined(ARCH_NRF52) #include "modules/ExternalNotificationModule.h" #if !defined(TTGO_T_ECHO) @@ -68,9 +66,7 @@ void setupModules() #endif #ifdef ARCH_ESP32 // Only run on an esp32 based device. -#ifdef USE_SX1280 new AudioModule(); -#endif new ExternalNotificationModule(); storeForwardModule = new StoreForwardModule(); diff --git a/src/modules/esp32/AudioModule.cpp b/src/modules/esp32/AudioModule.cpp index c37a1b2a8..0f95c306a 100644 --- a/src/modules/esp32/AudioModule.cpp +++ b/src/modules/esp32/AudioModule.cpp @@ -6,6 +6,8 @@ #include "Router.h" #include "FSCommon.h" +#include +#include #include /* @@ -30,7 +32,7 @@ KNOWN PROBLEMS * Until the module is initilized by the startup sequence, the amp_pin pin is in a floating - state. This may produce a bit of "noise". + radio_state. This may produce a bit of "noise". * Will not work on NRF and the Linux device targets. */ @@ -40,12 +42,20 @@ #define AUDIO_MODULE_RX_BUFFER 128 #define AUDIO_MODULE_DATA_MAX Constants_DATA_PAYLOAD_LEN -#define AUDIO_MODULE_MODE 7 // 700B -#define AUDIO_MODULE_ACK 1 +#define AUDIO_MODULE_MODE ModuleConfig_AudioConfig_Audio_Baud_CODEC2_700 -#if defined(ARCH_ESP32) && defined(USE_SX1280) +#if defined(ARCH_ESP32) AudioModule *audioModule; +Codec2Thread *codec2Thread; + +FastAudioFIFO audio_fifo; +uint16_t adc_buffer[ADC_BUFFER_SIZE] = {}; +uint16_t adc_buffer_index = 0; +portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED; +int16_t speech[ADC_BUFFER_SIZE] = {}; +volatile RadioState radio_state = RadioState::tx; +adc1_channel_t mic_chan = (adc1_channel_t)0; ButterworthFilter hp_filter(240, 8000, ButterworthFilter::ButterworthFilter::Highpass, 1); @@ -55,55 +65,22 @@ int Sine1KHz_index = 0; uint8_t rx_raw_audio_value = 127; -AudioModule::AudioModule() : SinglePortModule("AudioModule", PortNum_AUDIO_APP), concurrency::OSThread("AudioModule") { - audio_fifo.init(); +int IRAM_ATTR local_adc1_read(int channel) { + uint16_t adc_value; + SENS.sar_meas_start1.sar1_en_pad = (1 << channel); // only one channel is selected + while (SENS.sar_slave_addr1.meas_status != 0); + SENS.sar_meas_start1.meas1_start_sar = 0; + SENS.sar_meas_start1.meas1_start_sar = 1; + while (SENS.sar_meas_start1.meas1_done_sar == 0); + adc_value = SENS.sar_meas_start1.meas1_data_sar; + return adc_value; } -void AudioModule::run_codec2() +IRAM_ATTR void am_onTimer() { - if (state == State::tx) - { - for (int i = 0; i < ADC_BUFFER_SIZE; i++) - speech[i] = (int16_t)hp_filter.Update((float)speech[i]); - - codec2_encode(codec2_state, tx_encode_frame + tx_encode_frame_index, speech); - - //increment the pointer where the encoded frame must be saved - tx_encode_frame_index += 8; - - //If it is the 5th time then we have a ready trasnmission frame - if (tx_encode_frame_index == ENCODE_FRAME_SIZE) - { - tx_encode_frame_index = 0; - //Transmit it - sendPayload(); - } - } - if (state == State::rx) //Receiving - { - //Make a cycle to get each codec2 frame from the received frame - for (int i = 0; i < ENCODE_FRAME_SIZE; i += 8) - { - //Decode the codec2 frame - codec2_decode(codec2_state, output_buffer, rx_encode_frame + i); - - // Add to the audio buffer the 320 samples resulting of the decode of the codec2 frame. - for (int g = 0; g < ADC_BUFFER_SIZE; g++) - audio_fifo.put(output_buffer[g]); - } - } - state = State::standby; -} - -void AudioModule::handleInterrupt() -{ - audioModule->onTimer(); -} - -void AudioModule::onTimer() -{ - if (state == State::tx) { - adc_buffer[adc_buffer_index++] = (16 * adc1_get_raw(mic_chan)) - 32768; + portENTER_CRITICAL_ISR(&timerMux); //Enter crital code without interruptions + if (radio_state == RadioState::tx) { + adc_buffer[adc_buffer_index++] = (16 * local_adc1_read(mic_chan)) - 32768; //If you want to test with a 1KHz tone, comment the line above and descomment the three lines below @@ -113,39 +90,96 @@ void AudioModule::onTimer() if (adc_buffer_index == ADC_BUFFER_SIZE) { adc_buffer_index = 0; + DEBUG_MSG("--- memcpy\n"); memcpy((void*)speech, (void*)adc_buffer, 2 * ADC_BUFFER_SIZE); - audioModule->setIntervalFromNow(0); // process buffer immediately + // Notify codec2 task that the buffer is ready. + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + DEBUG_MSG("--- notifyFromISR\n"); + codec2Thread->notifyFromISR(&xHigherPriorityTaskWoken, RadioState::tx, true); + if (xHigherPriorityTaskWoken) + portYIELD_FROM_ISR(); } - } else if (state == State::rx) { + } else if (radio_state == RadioState::rx) { int16_t v; //Get a value from audio_fifo and convert it to 0 - 255 to play it in the ADC - //If none value is available the DAC will play the last one that was read, that's - //why the rx_raw_audio_value variable is a global one. if (audio_fifo.get(&v)) rx_raw_audio_value = (uint8_t)((v + 32768) / 256); - - //Play dacWrite(moduleConfig.audio.amp_pin ? moduleConfig.audio.amp_pin : AAMP, rx_raw_audio_value); } + portEXIT_CRITICAL_ISR(&timerMux); // exit critical code +} + +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); + codec2_set_lpc_post_filter(codec2_state, 1, 0, 0.8, 0.2); + } else { + DEBUG_MSG("--- Codec2 disabled\n"); + } +} + +AudioModule::AudioModule() : SinglePortModule("AudioModule", PortNum_AUDIO_APP), concurrency::OSThread("AudioModule") { + audio_fifo.init(); + new Codec2Thread(); +} + +void 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); + + //increment the pointer where the encoded frame must be saved + audioModule->tx_encode_frame_index += 8; + + //If it is the 5th time then we have a ready trasnmission frame + if (audioModule->tx_encode_frame_index == ENCODE_FRAME_SIZE) + { + audioModule->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) + { + //Decode the codec2 frame + codec2_decode(codec2_state, output_buffer, audioModule->rx_encode_frame + i); + + // Add to the audio buffer the 320 samples resulting of the decode of the codec2 frame. + for (int g = 0; g < ADC_BUFFER_SIZE; g++) + audio_fifo.put(output_buffer[g]); + } + break; + default: + assert(0); // We expected to receive a valid notification from the ISR + break; + } } int32_t AudioModule::runOnce() { - if (moduleConfig.audio.codec2_enabled) { - + if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) { if (firstTime) { - - DEBUG_MSG("Initializing ADC on Channel %u\n", moduleConfig.audio.mic_chan ? moduleConfig.audio.mic_chan : AMIC); + 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); + adc1_config_channel_atten(mic_chan, ADC_ATTEN_DB_6); + adc1_get_raw(mic_chan); + + radio_state = RadioState::rx; // Start a timer at 8kHz to sample the ADC and play the audio on the DAC. uint32_t cpufreq = getCpuFrequencyMhz(); - switch (cpufreq){ + switch (cpufreq){ case 160: adcTimer = timerBegin(3, 1000, true); // 160 MHz / 1000 = 160KHz break; @@ -160,48 +194,44 @@ int32_t AudioModule::runOnce() adcTimer = timerBegin(3, 500, true); // 80 MHz / 500 = 160KHz break; } - timerAttachInterrupt(adcTimer, &AudioModule::handleInterrupt, true); - timerAlarmWrite(adcTimer, 20, true); // Interrupts when counter == 20, 8.000 times a second - timerAlarmEnable(adcTimer); + 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); - DEBUG_MSG("Initializing DAC on Pin %u\n", moduleConfig.audio.amp_pin ? moduleConfig.audio.amp_pin : AAMP); - DEBUG_MSG("Initializing PTT on Pin %u\n", moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN); + DEBUG_MSG("--- Initializing DAC on Pin %u\n", moduleConfig.audio.amp_pin ? moduleConfig.audio.amp_pin : AAMP); + DEBUG_MSG("--- Initializing PTT on Pin %u\n", moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN); // Configure PTT input - pinMode(moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN, INPUT_PULLUP); + pinMode(moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN, INPUT); - state = State::rx; - - 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); - codec2_set_lpc_post_filter(codec2_state, 1, 0, 0.8, 0.2); - - firstTime = 0; + firstTime = false; } else { - // Check if we have a PTT press - if (digitalRead(moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN) == LOW) { - // PTT pressed, recording - state = State::tx; - } - if (state != State::standby) { - run_codec2(); + // 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"); + radio_state = RadioState::tx; + } + } else { + if (radio_state == RadioState::tx) { + DEBUG_MSG("--- PTT released, switching to RX\n"); + radio_state = RadioState::rx; + } } + } - return 100; } else { - DEBUG_MSG("Audio Module Disabled\n"); - + DEBUG_MSG("--- Audio Module Disabled\n"); return INT32_MAX; } + } MeshPacket *AudioModule::allocReply() { - auto reply = allocDataPacket(); // Allocate a packet for sending - return reply; } @@ -211,7 +241,8 @@ void AudioModule::sendPayload(NodeNum dest, bool wantReplies) p->to = dest; p->decoded.want_response = wantReplies; - p->want_ack = AUDIO_MODULE_ACK; + 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; memcpy(p->decoded.payload.bytes, tx_encode_frame, p->decoded.payload.size); @@ -221,16 +252,18 @@ void AudioModule::sendPayload(NodeNum dest, bool wantReplies) ProcessMessage AudioModule::handleReceived(const MeshPacket &mp) { - if (moduleConfig.audio.codec2_enabled) { + if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) { auto &p = mp.decoded; if (getFrom(&mp) != nodeDB.getNodeNum()) { if (p.payload.size == ENCODE_FRAME_SIZE) { memcpy(rx_encode_frame, p.payload.bytes, p.payload.size); - state = State::rx; - audioModule->setIntervalFromNow(0); - run_codec2(); + radio_state = RadioState::rx; + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + codec2Thread->notifyFromISR(&xHigherPriorityTaskWoken, RadioState::rx, true); + 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, ENCODE_FRAME_SIZE); } } } diff --git a/src/modules/esp32/AudioModule.h b/src/modules/esp32/AudioModule.h index d82af4d43..c83160cfb 100644 --- a/src/modules/esp32/AudioModule.h +++ b/src/modules/esp32/AudioModule.h @@ -7,38 +7,45 @@ #include #include #include -#if defined(ARCH_ESP32) && defined(USE_SX1280) +#if defined(ARCH_ESP32) #include #include #include #endif #define ADC_BUFFER_SIZE 320 // 40ms of voice in 8KHz sampling frequency -#define ENCODE_FRAME_SIZE 40 // 5 codec2 frames of 8 bytes each +#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 +{ +#if defined(ARCH_ESP32) + struct CODEC2* codec2_state = NULL; + int16_t output_buffer[ADC_BUFFER_SIZE] = {}; + + public: + Codec2Thread(); + + protected: + virtual void onNotify(uint32_t notification) override; +#endif +}; class AudioModule : public SinglePortModule, private concurrency::OSThread { -#if defined(ARCH_ESP32) && defined(USE_SX1280) - bool firstTime = 1; +#if defined(ARCH_ESP32) + bool firstTime = true; hw_timer_t* adcTimer = NULL; - uint16_t adc_buffer[ADC_BUFFER_SIZE] = {}; - int16_t speech[ADC_BUFFER_SIZE] = {}; - int16_t output_buffer[ADC_BUFFER_SIZE] = {}; - unsigned char rx_encode_frame[ENCODE_FRAME_SIZE] = {}; - unsigned char tx_encode_frame[ENCODE_FRAME_SIZE] = {}; - int tx_encode_frame_index = 0; + FastAudioFIFO audio_fifo; uint16_t adc_buffer_index = 0; - adc1_channel_t mic_chan = (adc1_channel_t)0; - struct CODEC2* codec2_state = NULL; - enum State - { - standby, rx, tx - }; - volatile State state = State::tx; public: + unsigned char rx_encode_frame[ENCODE_FRAME_SIZE] = {}; + unsigned char tx_encode_frame[ENCODE_FRAME_SIZE] = {}; + int tx_encode_frame_index = 0; + AudioModule(); /** @@ -49,11 +56,7 @@ class AudioModule : public SinglePortModule, private concurrency::OSThread protected: virtual int32_t runOnce() override; - static void handleInterrupt(); - - void onTimer(); - - void run_codec2(); + // void run_codec2(); virtual MeshPacket *allocReply() override; @@ -65,4 +68,16 @@ class AudioModule : public SinglePortModule, private concurrency::OSThread }; extern AudioModule *audioModule; +extern Codec2Thread *codec2Thread; + +extern FastAudioFIFO audio_fifo; +extern uint16_t adc_buffer[ADC_BUFFER_SIZE]; +extern uint16_t adc_buffer_index; +extern portMUX_TYPE timerMux; +extern int16_t speech[ADC_BUFFER_SIZE]; +enum RadioState { standby, rx, tx }; +extern volatile RadioState radio_state; +extern adc1_channel_t mic_chan; + +IRAM_ATTR void am_onTimer();