mirror of
https://github.com/meshtastic/firmware.git
synced 2025-04-30 11:23:53 +00:00
Still WIP, but first working version of audio. I2S works good, analogue not so much.
This commit is contained in:
parent
fb89828990
commit
4b63730efb
@ -66,7 +66,7 @@ void setupModules()
|
|||||||
#endif
|
#endif
|
||||||
#ifdef ARCH_ESP32
|
#ifdef ARCH_ESP32
|
||||||
// Only run on an esp32 based device.
|
// Only run on an esp32 based device.
|
||||||
new AudioModule();
|
audioModule = new AudioModule();
|
||||||
new ExternalNotificationModule();
|
new ExternalNotificationModule();
|
||||||
|
|
||||||
storeForwardModule = new StoreForwardModule();
|
storeForwardModule = new StoreForwardModule();
|
||||||
|
@ -23,25 +23,36 @@
|
|||||||
|
|
||||||
Basic Usage:
|
Basic Usage:
|
||||||
1) Enable the module by setting audio.codec2_enabled to 1.
|
1) Enable the module by setting audio.codec2_enabled to 1.
|
||||||
2) Set the pins (audio.mic_pin / audio.amp_pin) for your preferred microphone and amplifier GPIO pins.
|
2a) Set the pins for the I2S interface if you want to use digital audio. Recommended on TLora is I2S_WS 13/I2S_SD 15/I2S_SIN 2/I2S_SCK 14
|
||||||
On tbeam, recommend to use:
|
2b) Set the pins (audio.mic_pin / audio.amp_pin) for your preferred microphone and amplifier GPIO pins if you want to use analog audio.
|
||||||
|
This is rather heavy on the CPU and not recommended.
|
||||||
|
On tbeam, best use:
|
||||||
audio.mic_chan 6 (GPIO 34)
|
audio.mic_chan 6 (GPIO 34)
|
||||||
audio.amp_pin 14
|
audio.amp_pin 14
|
||||||
audio.ptt_pin 39
|
audio.ptt_pin 39
|
||||||
3) Set audio.timeout to the amount of time to wait before we consider
|
3) Set audio.bitrate to the desired codec2 rate (CODEC2_3200, CODEC2_2400, CODEC2_1600, CODEC2_1400, CODEC2_1300, CODEC2_1200, CODEC2_700, CODEC2_700B)
|
||||||
your voice stream as "done".
|
|
||||||
4) Set audio.bitrate to the desired codec2 rate (CODEC2_3200, CODEC2_2400, CODEC2_1600, CODEC2_1400, CODEC2_1300, CODEC2_1200, CODEC2_700, CODEC2_700B)
|
|
||||||
|
|
||||||
KNOWN PROBLEMS
|
KNOWN PROBLEMS
|
||||||
* Until the module is initilized by the startup sequence, the amp_pin pin is in a floating
|
* Until the module is initilized by the startup sequence, the amp_pin is in a floating
|
||||||
radio_state. This may produce a bit of "noise".
|
state. This may produce a bit of "noise".
|
||||||
* Will not work on NRF and the Linux device targets.
|
* Will not work on NRF and the Linux device targets (yet?).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define AMIC 6
|
#define AMIC 6
|
||||||
#define AAMP 14
|
#define AAMP 14
|
||||||
#define PTT_PIN 39
|
#define PTT_PIN 39
|
||||||
|
|
||||||
|
ButterworthFilter hp_filter(240, 8000, ButterworthFilter::ButterworthFilter::Highpass, 1);
|
||||||
|
|
||||||
|
// Use I2S Processor 0
|
||||||
|
#define I2S_PORT I2S_NUM_0
|
||||||
|
|
||||||
|
#define AUDIO_MODULE_RX_BUFFER 128
|
||||||
|
#define AUDIO_MODULE_MODE ModuleConfig_AudioConfig_Audio_Baud_CODEC2_700
|
||||||
|
|
||||||
|
TaskHandle_t codec2HandlerTask;
|
||||||
|
AudioModule *audioModule;
|
||||||
|
|
||||||
#ifdef ARCH_ESP32
|
#ifdef ARCH_ESP32
|
||||||
// ESP32 doesn't use that flag
|
// ESP32 doesn't use that flag
|
||||||
#define YIELD_FROM_ISR(x) portYIELD_FROM_ISR()
|
#define YIELD_FROM_ISR(x) portYIELD_FROM_ISR()
|
||||||
@ -49,36 +60,13 @@
|
|||||||
#define YIELD_FROM_ISR(x) portYIELD_FROM_ISR(x)
|
#define YIELD_FROM_ISR(x) portYIELD_FROM_ISR(x)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// #define I2S_WS 13
|
|
||||||
// #define I2S_SD 15
|
|
||||||
// #define I2S_SIN 2
|
|
||||||
// #define I2S_SCK 14
|
|
||||||
|
|
||||||
// Use I2S Processor 0
|
|
||||||
#define I2S_PORT I2S_NUM_0
|
|
||||||
|
|
||||||
#define AUDIO_MODULE_RX_BUFFER 128
|
|
||||||
#define AUDIO_MODULE_MODE ModuleConfig_AudioConfig_Audio_Baud_CODEC2_700
|
|
||||||
|
|
||||||
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;
|
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
|
||||||
int16_t speech[ADC_BUFFER_SIZE] = {};
|
hw_timer_t* adcTimer = NULL;
|
||||||
volatile RadioState radio_state = RadioState::tx;
|
|
||||||
adc1_channel_t mic_chan = (adc1_channel_t)0;
|
|
||||||
|
|
||||||
ButterworthFilter hp_filter(240, 8000, ButterworthFilter::ButterworthFilter::Highpass, 1);
|
|
||||||
|
|
||||||
//int16_t 1KHz sine test tone
|
//int16_t 1KHz sine test tone
|
||||||
int16_t Sine1KHz[8] = { -21210 , -30000, -21210, 0 , 21210 , 30000 , 21210, 0 };
|
int16_t Sine1KHz[8] = { -21210 , -30000, -21210, 0 , 21210 , 30000 , 21210, 0 };
|
||||||
int Sine1KHz_index = 0;
|
int Sine1KHz_index = 0;
|
||||||
|
|
||||||
uint8_t rx_raw_audio_value = 127;
|
|
||||||
|
|
||||||
int IRAM_ATTR local_adc1_read(int channel) {
|
int IRAM_ATTR local_adc1_read(int channel) {
|
||||||
uint16_t adc_value;
|
uint16_t adc_value;
|
||||||
#if CONFIG_IDF_TARGET_ESP32S3
|
#if CONFIG_IDF_TARGET_ESP32S3
|
||||||
@ -102,8 +90,8 @@ int IRAM_ATTR local_adc1_read(int channel) {
|
|||||||
IRAM_ATTR void am_onTimer()
|
IRAM_ATTR void am_onTimer()
|
||||||
{
|
{
|
||||||
portENTER_CRITICAL_ISR(&timerMux); //Enter crital code without interruptions
|
portENTER_CRITICAL_ISR(&timerMux); //Enter crital code without interruptions
|
||||||
if ((radio_state == RadioState::tx) && (!moduleConfig.audio.i2s_sd)) {
|
if ((audioModule->radio_state == RadioState::tx) && (!moduleConfig.audio.i2s_sd)) {
|
||||||
adc_buffer[adc_buffer_index++] = (16 * local_adc1_read(mic_chan)) - 32768;
|
audioModule->adc_buffer[audioModule->adc_buffer_index++] = (16 * local_adc1_read(audioModule->mic_chan)) - 32768;
|
||||||
|
|
||||||
//If you want to test with a 1KHz tone, comment the line above and descomment the three lines below
|
//If you want to test with a 1KHz tone, comment the line above and descomment the three lines below
|
||||||
|
|
||||||
@ -111,91 +99,90 @@ IRAM_ATTR void am_onTimer()
|
|||||||
// if (Sine1KHz_index >= 8)
|
// if (Sine1KHz_index >= 8)
|
||||||
// Sine1KHz_index = 0;
|
// Sine1KHz_index = 0;
|
||||||
|
|
||||||
if (adc_buffer_index == ADC_BUFFER_SIZE) {
|
if (audioModule->adc_buffer_index == audioModule->adc_buffer_size) {
|
||||||
adc_buffer_index = 0;
|
audioModule->adc_buffer_index = 0;
|
||||||
DEBUG_MSG("♪♫♪ memcpy\n");
|
memcpy((void*)audioModule->speech, (void*)audioModule->adc_buffer, 2 * audioModule->adc_buffer_size);
|
||||||
memcpy((void*)speech, (void*)adc_buffer, 2 * ADC_BUFFER_SIZE);
|
|
||||||
// Notify codec2 task that the buffer is ready.
|
|
||||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||||
DEBUG_MSG("♪♫♪ notifyFromISR\n");
|
vTaskNotifyGiveFromISR(codec2HandlerTask, &xHigherPriorityTaskWoken);
|
||||||
codec2Thread->notifyFromISR(&xHigherPriorityTaskWoken, RadioState::tx, true);
|
if (xHigherPriorityTaskWoken == pdTRUE)
|
||||||
if (xHigherPriorityTaskWoken)
|
YIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
||||||
portYIELD_FROM_ISR();
|
|
||||||
}
|
}
|
||||||
} else if ((radio_state == RadioState::rx) && (!moduleConfig.audio.i2s_din)) {
|
} else if ((audioModule->radio_state == RadioState::rx) && (!moduleConfig.audio.i2s_din)) {
|
||||||
// ESP32-S3 does not have DAC support
|
// ESP32-S3 does not have DAC support
|
||||||
#if !defined(CONFIG_IDF_TARGET_ESP32S3)
|
#if !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||||
int16_t v;
|
|
||||||
|
|
||||||
//Get a value from audio_fifo and convert it to 0 - 255 to play it in the ADC
|
//Get a value from audio_fifo and convert it to 0 - 255 to play it in the ADC
|
||||||
if (audio_fifo.get(&v))
|
if (audioModule->fifo.get(&audioModule->sample))
|
||||||
rx_raw_audio_value = (uint8_t)((v + 32768) / 256);
|
audioModule->rx_raw_audio_value = (uint8_t)((audioModule->sample + 32768) / 256);
|
||||||
|
|
||||||
dacWrite(moduleConfig.audio.amp_pin ? moduleConfig.audio.amp_pin : AAMP, rx_raw_audio_value);
|
dacWrite(moduleConfig.audio.amp_pin ? moduleConfig.audio.amp_pin : AAMP, audioModule->rx_raw_audio_value);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
portEXIT_CRITICAL_ISR(&timerMux); // exit critical code
|
portEXIT_CRITICAL_ISR(&timerMux); // exit critical code
|
||||||
}
|
}
|
||||||
|
|
||||||
Codec2Thread::Codec2Thread() : concurrency::NotifiedWorkerThread("Codec2Thread") {
|
void run_codec2(void* parameter)
|
||||||
if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) {
|
{
|
||||||
DEBUG_MSG("♪♫♪ Setting up codec2 in mode %u", (moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1);
|
while (true) {
|
||||||
codec2_state = codec2_create((moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1);
|
uint32_t tcount = ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(10000));
|
||||||
codec2_set_lpc_post_filter(codec2_state, 1, 0, 0.8, 0.2);
|
|
||||||
encode_codec_size = (codec2_bits_per_frame(codec2_state) + 7) / 8;
|
if (tcount != 0) {
|
||||||
encode_frame_num = Constants_DATA_PAYLOAD_LEN / encode_codec_size;
|
if (audioModule->radio_state == RadioState::tx) {
|
||||||
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);
|
for (int i = 0; i < audioModule->adc_buffer_size; i++)
|
||||||
} else {
|
audioModule->speech[i] = (int16_t)hp_filter.Update((float)audioModule->speech[i]);
|
||||||
DEBUG_MSG("♪♫♪ Codec2 disabled (AudioModule %d, Region %s, permitted %d)\n", moduleConfig.audio.codec2_enabled, myRegion->name, myRegion->audioPermitted);
|
|
||||||
|
codec2_encode(audioModule->codec2, audioModule->tx_encode_frame + audioModule->tx_encode_frame_index, audioModule->speech);
|
||||||
|
|
||||||
|
//increment the pointer where the encoded frame must be saved
|
||||||
|
audioModule->tx_encode_frame_index += audioModule->encode_codec_size;
|
||||||
|
|
||||||
|
//If it this is reached we have a ready trasnmission frame
|
||||||
|
if (audioModule->tx_encode_frame_index == audioModule->encode_frame_size)
|
||||||
|
{
|
||||||
|
//Transmit it
|
||||||
|
DEBUG_MSG("♪♫♪ Sending %d bytes\n", audioModule->encode_frame_size);
|
||||||
|
audioModule->sendPayload();
|
||||||
|
audioModule->tx_encode_frame_index = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (audioModule->radio_state == RadioState::rx) {
|
||||||
|
//Make a cycle to get each codec2 frame from the received frame
|
||||||
|
for (int i = 0; i < audioModule->rx_encode_frame_index; i += audioModule->encode_codec_size)
|
||||||
|
{
|
||||||
|
//Decode the codec2 frame
|
||||||
|
codec2_decode(audioModule->codec2, audioModule->output_buffer, audioModule->rx_encode_frame + i);
|
||||||
|
|
||||||
|
if (moduleConfig.audio.i2s_din) {
|
||||||
|
size_t bytesOut = 0;
|
||||||
|
|
||||||
|
i2s_write(I2S_PORT, &audioModule->output_buffer, audioModule->adc_buffer_size, &bytesOut, pdMS_TO_TICKS(40));
|
||||||
|
} else {
|
||||||
|
//Put the decoded audio in the fifo
|
||||||
|
for (int j = 0; j < audioModule->adc_buffer_size; j++)
|
||||||
|
audioModule->fifo.put(audioModule->output_buffer[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioModule::AudioModule() : SinglePortModule("AudioModule", PortNum_AUDIO_APP), concurrency::OSThread("AudioModule") {
|
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 IRAM_ATTR Codec2Thread::onNotify(uint32_t notification)
|
|
||||||
{
|
{
|
||||||
switch (notification) {
|
if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) {
|
||||||
case RadioState::tx:
|
fifo.init();
|
||||||
for (int i = 0; i < ADC_BUFFER_SIZE; i++)
|
DEBUG_MSG("♪♫♪ Setting up codec2 in mode %u", (moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1);
|
||||||
speech[i] = (int16_t)hp_filter.Update((float)speech[i]);
|
codec2 = codec2_create((moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1);
|
||||||
|
codec2_set_lpc_post_filter(codec2, 1, 0, 0.8, 0.2);
|
||||||
codec2_encode(codec2_state, audioModule->tx_encode_frame + tx_encode_frame_index, speech);
|
encode_codec_size = (codec2_bits_per_frame(codec2) + 7) / 8;
|
||||||
|
encode_frame_num = Constants_DATA_PAYLOAD_LEN / encode_codec_size;
|
||||||
//increment the pointer where the encoded frame must be saved
|
encode_frame_size = encode_frame_num * encode_codec_size; // max 237 bytes
|
||||||
tx_encode_frame_index += encode_codec_size;
|
adc_buffer_size = codec2_samples_per_frame(codec2);
|
||||||
|
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);
|
||||||
//If it this is reached we have a ready trasnmission frame
|
xTaskCreate(&run_codec2, "codec2_task", 30000, NULL, 5, &codec2HandlerTask);
|
||||||
if (tx_encode_frame_index == encode_frame_size)
|
} else {
|
||||||
{
|
DEBUG_MSG("♪♫♪ Codec2 disabled (AudioModule %d, Region %s, permitted %d)\n", moduleConfig.audio.codec2_enabled, myRegion->name, myRegion->audioPermitted);
|
||||||
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_codec_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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,9 +201,9 @@ int32_t AudioModule::runOnce()
|
|||||||
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
|
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
|
||||||
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
|
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
|
||||||
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S),
|
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S),
|
||||||
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
|
.intr_alloc_flags = 0,
|
||||||
.dma_buf_count = 8,
|
.dma_buf_count = 8,
|
||||||
.dma_buf_len = ADC_BUFFER_SIZE, // 320 * 2 bytes
|
.dma_buf_len = adc_buffer_size, // 320 * 2 bytes
|
||||||
.use_apll = false,
|
.use_apll = false,
|
||||||
.tx_desc_auto_clear = true,
|
.tx_desc_auto_clear = true,
|
||||||
.fixed_mclk = 0
|
.fixed_mclk = 0
|
||||||
@ -239,9 +226,20 @@ int32_t AudioModule::runOnce()
|
|||||||
if(res != ESP_OK)
|
if(res != ESP_OK)
|
||||||
DEBUG_MSG("♪♫♪ Failed to start I2S: %d\n", res);
|
DEBUG_MSG("♪♫♪ Failed to start I2S: %d\n", res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
#else
|
||||||
|
if (!moduleConfig.audio.i2s_din) {
|
||||||
|
DEBUG_MSG("♪♫♪ ESP32-S3 does not support DAC. Audio Module Disabled.\n");
|
||||||
|
return INT32_MAX;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!moduleConfig.audio.i2s_sd) {
|
if (!moduleConfig.audio.i2s_sd) {
|
||||||
// Set up ADC if we don't have a digital microphone.
|
// 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);
|
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;
|
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_width(ADC_WIDTH_12Bit);
|
||||||
@ -249,8 +247,6 @@ int32_t AudioModule::runOnce()
|
|||||||
adc1_get_raw(mic_chan);
|
adc1_get_raw(mic_chan);
|
||||||
}
|
}
|
||||||
|
|
||||||
radio_state = RadioState::rx;
|
|
||||||
|
|
||||||
if ((!moduleConfig.audio.i2s_sd) || (!moduleConfig.audio.i2s_din)) {
|
if ((!moduleConfig.audio.i2s_sd) || (!moduleConfig.audio.i2s_din)) {
|
||||||
// Start a timer at 8kHz to sample the ADC and play the audio on the DAC, but only if we have analogue audio to process
|
// Start a timer at 8kHz to sample the ADC and play the audio on the DAC, but only if we have analogue audio to process
|
||||||
uint32_t cpufreq = getCpuFrequencyMhz();
|
uint32_t cpufreq = getCpuFrequencyMhz();
|
||||||
@ -275,11 +271,8 @@ int32_t AudioModule::runOnce()
|
|||||||
timerAlarmEnable(adcTimer);
|
timerAlarmEnable(adcTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup analogue DAC only if we don't use I2S for output. This is not available on ESP32-S3
|
radio_state = RadioState::rx;
|
||||||
#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);
|
|
||||||
#endif
|
|
||||||
// Configure PTT input
|
// 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);
|
pinMode(moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN, INPUT);
|
||||||
@ -294,23 +287,32 @@ int32_t AudioModule::runOnce()
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (radio_state == RadioState::tx) {
|
if (radio_state == RadioState::tx) {
|
||||||
DEBUG_MSG("♪♫♪ PTT released, switching to RX\n");
|
if (tx_encode_frame_index > 0) {
|
||||||
|
// Send the incomplete frame
|
||||||
|
DEBUG_MSG("♪♫♪ Sending %d bytes (incomplete)\n", tx_encode_frame_index);
|
||||||
|
sendPayload();
|
||||||
|
}
|
||||||
|
DEBUG_MSG("♪♫♪ PTT released, switching to RX\n");
|
||||||
|
tx_encode_frame_index = 0;
|
||||||
radio_state = RadioState::rx;
|
radio_state = RadioState::rx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((radio_state == RadioState::tx) && moduleConfig.audio.i2s_sd) {
|
if ((radio_state == RadioState::tx) && moduleConfig.audio.i2s_sd) {
|
||||||
// Get I2S data from the microphone and place in data buffer
|
// Get I2S data from the microphone and place in data buffer
|
||||||
size_t bytesIn = 0;
|
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.
|
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) {
|
if (res == ESP_OK) {
|
||||||
adc_buffer_index += bytesIn;
|
adc_buffer_index += bytesIn;
|
||||||
if (adc_buffer_index == ADC_BUFFER_SIZE) {
|
if (adc_buffer_index == adc_buffer_size) {
|
||||||
adc_buffer_index = 0;
|
adc_buffer_index = 0;
|
||||||
DEBUG_MSG("♪♫♪ We have a full buffer, process it\n");
|
memcpy((void*)speech, (void*)adc_buffer, 2 * adc_buffer_size);
|
||||||
memcpy((void*)speech, (void*)adc_buffer, 2 * ADC_BUFFER_SIZE);
|
// Notify run_codec2 task that the buffer is ready.
|
||||||
// Notify codec2 task that the buffer is ready.
|
radio_state = RadioState::tx;
|
||||||
codec2Thread->notify(RadioState::tx, true);
|
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||||
|
vTaskNotifyGiveFromISR(codec2HandlerTask, &xHigherPriorityTaskWoken);
|
||||||
|
if (xHigherPriorityTaskWoken == pdTRUE)
|
||||||
|
YIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -338,7 +340,7 @@ void AudioModule::sendPayload(NodeNum dest, bool wantReplies)
|
|||||||
p->want_ack = false; // Audio is shoot&forget. TODO: Is this really suppressing retransmissions?
|
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->priority = MeshPacket_Priority_MAX; // Audio is important, because realtime
|
||||||
|
|
||||||
p->decoded.payload.size = codec2Thread->get_encode_frame_size();
|
p->decoded.payload.size = tx_encode_frame_index;
|
||||||
memcpy(p->decoded.payload.bytes, tx_encode_frame, p->decoded.payload.size);
|
memcpy(p->decoded.payload.bytes, tx_encode_frame, p->decoded.payload.size);
|
||||||
|
|
||||||
service.sendToMesh(p);
|
service.sendToMesh(p);
|
||||||
@ -349,16 +351,14 @@ ProcessMessage AudioModule::handleReceived(const MeshPacket &mp)
|
|||||||
if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) {
|
if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) {
|
||||||
auto &p = mp.decoded;
|
auto &p = mp.decoded;
|
||||||
if (getFrom(&mp) != nodeDB.getNodeNum()) {
|
if (getFrom(&mp) != nodeDB.getNodeNum()) {
|
||||||
if (p.payload.size == codec2Thread->get_encode_frame_size()) {
|
memcpy(rx_encode_frame, p.payload.bytes, p.payload.size);
|
||||||
memcpy(rx_encode_frame, p.payload.bytes, p.payload.size);
|
radio_state = RadioState::rx;
|
||||||
radio_state = RadioState::rx;
|
rx_encode_frame_index = p.payload.size;
|
||||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
// Notify run_codec2 task that the buffer is ready.
|
||||||
codec2Thread->notifyFromISR(&xHigherPriorityTaskWoken, RadioState::rx, true);
|
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||||
if (xHigherPriorityTaskWoken)
|
vTaskNotifyGiveFromISR(codec2HandlerTask, &xHigherPriorityTaskWoken);
|
||||||
portYIELD_FROM_ISR();
|
if (xHigherPriorityTaskWoken == pdTRUE)
|
||||||
} else {
|
YIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
||||||
DEBUG_MSG("♪♫♪ Invalid payload size %u != %u\n", p.payload.size, codec2Thread->get_encode_frame_size());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,41 +7,36 @@
|
|||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <driver/i2s.h>
|
#include <driver/i2s.h>
|
||||||
#include <driver/adc.h>
|
// #include <driver/adc.h>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <codec2.h>
|
#include <codec2.h>
|
||||||
#include <ButterworthFilter.h>
|
#include <ButterworthFilter.h>
|
||||||
#include <FastAudioFIFO.h>
|
#include <FastAudioFIFO.h>
|
||||||
|
|
||||||
#define ADC_BUFFER_SIZE 320 // 40ms of voice in 8KHz sampling frequency
|
#define ADC_BUFFER_SIZE_MAX 320
|
||||||
|
|
||||||
class Codec2Thread : public concurrency::NotifiedWorkerThread
|
enum RadioState { standby, rx, tx };
|
||||||
{
|
|
||||||
struct CODEC2* codec2_state = NULL;
|
|
||||||
int16_t output_buffer[ADC_BUFFER_SIZE] = {};
|
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
class AudioModule : public SinglePortModule, private concurrency::OSThread
|
class AudioModule : public SinglePortModule, private concurrency::OSThread
|
||||||
{
|
{
|
||||||
bool firstTime = true;
|
|
||||||
hw_timer_t* adcTimer = NULL;
|
|
||||||
uint16_t adc_buffer_index = 0;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
unsigned char rx_encode_frame[Constants_DATA_PAYLOAD_LEN] = {};
|
unsigned char rx_encode_frame[Constants_DATA_PAYLOAD_LEN] = {};
|
||||||
unsigned char tx_encode_frame[Constants_DATA_PAYLOAD_LEN] = {};
|
unsigned char tx_encode_frame[Constants_DATA_PAYLOAD_LEN] = {};
|
||||||
|
int16_t speech[ADC_BUFFER_SIZE_MAX] = {};
|
||||||
|
int16_t output_buffer[ADC_BUFFER_SIZE_MAX] = {};
|
||||||
|
uint16_t adc_buffer[ADC_BUFFER_SIZE_MAX] = {};
|
||||||
|
int adc_buffer_size = 0;
|
||||||
|
uint16_t adc_buffer_index = 0;
|
||||||
|
int tx_encode_frame_index = 0;
|
||||||
|
int rx_encode_frame_index = 0;
|
||||||
|
int encode_codec_size = 0;
|
||||||
|
int encode_frame_size = 0;
|
||||||
|
volatile RadioState radio_state = RadioState::rx;
|
||||||
|
FastAudioFIFO fifo;
|
||||||
|
struct CODEC2* codec2 = NULL;
|
||||||
|
int16_t sample;
|
||||||
|
adc1_channel_t mic_chan = (adc1_channel_t)0;
|
||||||
|
uint8_t rx_raw_audio_value = 127;
|
||||||
|
|
||||||
AudioModule();
|
AudioModule();
|
||||||
|
|
||||||
@ -51,9 +46,11 @@ class AudioModule : public SinglePortModule, private concurrency::OSThread
|
|||||||
void sendPayload(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
|
void sendPayload(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual int32_t runOnce() override;
|
int encode_frame_num = 0;
|
||||||
|
bool firstTime = true;
|
||||||
|
|
||||||
// void run_codec2();
|
|
||||||
|
virtual int32_t runOnce() override;
|
||||||
|
|
||||||
virtual MeshPacket *allocReply() override;
|
virtual MeshPacket *allocReply() override;
|
||||||
|
|
||||||
@ -64,15 +61,5 @@ class AudioModule : public SinglePortModule, private concurrency::OSThread
|
|||||||
};
|
};
|
||||||
|
|
||||||
extern AudioModule *audioModule;
|
extern AudioModule *audioModule;
|
||||||
extern Codec2Thread *codec2Thread;
|
|
||||||
|
|
||||||
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();
|
|
||||||
#endif
|
#endif
|
Loading…
Reference in New Issue
Block a user