mirror of
https://github.com/meshtastic/firmware.git
synced 2025-04-23 17:13:38 +00:00
Definition cleanup and AudioModule WIP
This commit is contained in:
parent
dbefa71bc8
commit
fb89828990
@ -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
|
||||
|
@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "../concurrency/NotifiedWorkerThread.h"
|
||||
#include "MemoryPool.h"
|
||||
#include "MeshTypes.h"
|
||||
#include "Observer.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 <RadioLib.h>
|
||||
|
||||
// ESP32 has special rules about ISR code
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 <FastAudioFIFO.h>
|
||||
|
||||
#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();
|
||||
|
||||
|
@ -4,7 +4,6 @@
|
||||
#include "MeshPacketQueue.h"
|
||||
#include "wifi/WiFiServerAPI.h"
|
||||
|
||||
#define RADIOLIB_EXCLUDE_HTTP
|
||||
#include <RadioLib.h>
|
||||
|
||||
class SimRadio : public RadioInterface
|
||||
|
Loading…
Reference in New Issue
Block a user