Merge pull request #16227 from gemu2015/i2s_update

I2s bridge
This commit is contained in:
Theo Arends 2022-08-15 22:43:57 +02:00 committed by GitHub
commit 86f963a319
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 341 additions and 24 deletions

View File

@ -109,6 +109,10 @@ void W8960_SetGain(uint8_t sel, uint16_t value) {
W8960_Write(0x15, value);
W8960_Write(0x16, value);
break;
case 3:
// audio interface
W8960_Write(0x07, value);
break;
}
}
#endif // ESP32

View File

@ -67,14 +67,25 @@
#define i2s_port_t uint8_t
#endif
#define MODE_MIC 0
#define MODE_SPK 1
#define MODE_MIC 1
#define MODE_SPK 2
#ifndef MICSRATE
#define MICSRATE 32000
#endif
typedef union {
uint8_t data;
struct {
uint8_t master : 1;
uint8_t enabled : 1;
uint8_t swap_mic : 1;
uint8_t mode : 2;
};
} BRIDGE_MODE;
struct AUDIO_I2S_t {
uint8_t is2_volume; // should be in settings
i2s_port_t i2s_port;
@ -134,6 +145,16 @@ struct AUDIO_I2S_t {
uint8_t mode;
#ifdef I2S_BRIDGE
BRIDGE_MODE bridge_mode;
WiFiUDP i2s_bridge_udp;
WiFiUDP i2s_bridgec_udp;
IPAddress i2s_bridge_ip;
TaskHandle_t i2s_bridge_h;
int8_t ptt_pin = -1;
#endif
} audio_i2s;
#ifndef MIC_CHANNELS
@ -308,11 +329,6 @@ int32_t I2S_Init_0(void) {
audio_i2s.mode = MODE_SPK;
#ifdef USE_I2S_COMMON_IO
audio_i2s.out->SetRate(MICSRATE);
#endif
return 0;
}
@ -327,11 +343,12 @@ void I2S_Init(void) {
#endif
#ifdef USE_W8960
W8960_Init();
W8960_Init1();
#endif
audio_i2s.is2_volume = 10;
audio_i2s.out->SetGain(((float)audio_i2s.is2_volume / 100.0) * 4.0);
audio_i2s.out->begin();
audio_i2s.out->stop();
audio_i2s.mp3ram = nullptr;
@ -356,7 +373,12 @@ void I2S_Init(void) {
audio_i2s.mic_channels = MIC_CHANNELS;
audio_i2s.mic_rate = MICSRATE;
#ifdef USE_I2S_COMMON_IO
i2s_set_clk(audio_i2s.mic_port, audio_i2s.mic_rate, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_STEREO);
#endif
#endif // ESP32
}
#ifdef ESP32
@ -589,12 +611,15 @@ const char kI2SAudio_Commands[] PROGMEM = "I2S|"
#ifdef USE_I2S_WEBRADIO
"|WR"
#endif // USE_I2S_WEBRADIO
#if defined(USE_SHINE) && ( (defined(USE_I2S_AUDIO) && defined(USE_I2S_MIC)) || defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX) )
#if ( (defined(USE_I2S_AUDIO) && defined(USE_I2S_MIC)) || defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX) )
"|REC"
"|MGain"
#ifdef MP3_MIC_STREAM
#if defined(USE_SHINE) && defined(MP3_MIC_STREAM)
"|STREAM"
#endif // MP3_MIC_STREAM
#ifdef I2S_BRIDGE
"|BRIDGE"
#endif // I2S_BRIDGE
#endif // USE_SHINE
#endif // ESP32
;
@ -609,12 +634,15 @@ void (* const I2SAudio_Command[])(void) PROGMEM = {
#ifdef USE_I2S_WEBRADIO
,&Cmd_WebRadio
#endif // USE_I2S_WEBRADIO
#if defined(USE_SHINE) && ( (defined(USE_I2S_AUDIO) && defined(USE_I2S_MIC)) || defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX) )
#if ( (defined(USE_I2S_AUDIO) && defined(USE_I2S_MIC)) || defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX) )
,&Cmd_MicRec
,&Cmd_MicGain
#ifdef MP3_MIC_STREAM
#if defined(USE_SHINE) && defined(MP3_MIC_STREAM)
,&Cmd_MP3Stream
#endif // MP3_MIC_STREAM
#ifdef I2S_BRIDGE
,&Cmd_I2SBridge
#endif // I2S_BRIDGE
#endif // USE_SHINE
#endif // ESP32
};
@ -661,19 +689,27 @@ bool Xdrv42(uint8_t function) {
case FUNC_INIT:
I2S_Init();
break;
#if defined(MP3_MIC_STREAM)
//#if defined(USE_SHINE) && defined(MP3_MIC_STREAM)
case FUNC_WEB_ADD_MAIN_BUTTON:
//MP3ShowStream();
break;
case FUNC_LOOP:
#if defined(USE_SHINE) && defined(MP3_MIC_STREAM)
i2s_mp3_loop();
#endif
#if defined(I2S_BRIDGE) && ( (defined(USE_I2S_AUDIO) && defined(USE_I2S_MIC)) || defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX) )
i2s_bridge_loop();
#endif
break;
case FUNC_WEB_ADD_HANDLER:
#if defined(USE_SHINE) && defined(MP3_MIC_STREAM)
audio_i2s.stream_enable = 1;
i2s_mp3_init(1);
break;
#endif
#if defined(I2S_BRIDGE) && ( (defined(USE_I2S_AUDIO) && defined(USE_I2S_MIC)) || defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX) )
I2SBridgeInit();
#endif
break;
#ifdef USE_WEBSERVER
#ifdef USE_I2S_WEBRADIO
case FUNC_WEB_SENSOR:

View File

@ -19,7 +19,7 @@
#ifdef ESP32
#if defined(USE_SHINE) && ( (defined(USE_I2S_AUDIO) && defined(USE_I2S_MIC)) || defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX) )
#if ( (defined(USE_I2S_AUDIO) && defined(USE_I2S_MIC)) || defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX) )
uint32_t SpeakerMic(uint8_t spkr) {
@ -27,7 +27,11 @@ esp_err_t err = ESP_OK;
#ifndef USE_I2S_COMMON_IO
if (audio_i2s.mode == spkr) {
return 0;
}
if (spkr == MODE_SPK) {
if (audio_i2s.i2s_port == audio_i2s.mic_port) {
if (audio_i2s.mode != MODE_SPK) {
i2s_driver_uninstall(audio_i2s.mic_port);
@ -61,24 +65,26 @@ esp_err_t err = ESP_OK;
.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT,
.communication_format = I2S_COMM_FORMAT_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
//.dma_buf_count = 8,
.dma_buf_count = 2,
//.dma_buf_len = 128,
.dma_buf_len = 1024,
.use_apll = 0, // Use audio PLL
.tx_desc_auto_clear = true,
.fixed_mclk = 0,
.fixed_mclk = 12000000,
.mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT, // I2S_MCLK_MULTIPLE_128
.bits_per_chan = I2S_BITS_PER_CHAN_16BIT
};
#ifdef ESP32S3_BOX
#ifdef USE_I2S_MIC
// mic select to GND
i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_TX);
i2s_config.communication_format = I2S_COMM_FORMAT_STAND_I2S;
#endif
#ifdef USE_I2S_MIC
// mic select to GND
i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX);
#ifdef ESP32S3_BOX
i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_TX);
i2s_config.communication_format = I2S_COMM_FORMAT_STAND_I2S;
#endif
@ -117,8 +123,8 @@ esp_err_t err = ESP_OK;
return err;
}
#ifdef USE_SHINE
#include <layer3.h>
#include <types.h>
@ -306,6 +312,8 @@ void Cmd_MicRec(void) {
}
}
#endif // USE_SHINE
// mic gain in factor not percent
void Cmd_MicGain(void) {
@ -315,6 +323,5 @@ void Cmd_MicGain(void) {
ResponseCmndNumber(audio_i2s.mic_gain);
}
#endif // USE_SHINE
#endif // USE_I2S_AUDIO
#endif // ESP32

View File

@ -142,7 +142,7 @@ void S3boxInit(void) {
#include <wm8960.h>
void W8960_Init(void) {
void W8960_Init1(void) {
if (TasmotaGlobal.i2c_enabled_2) {
if (I2cSetDevice(W8960_ADDR, 1)) {
I2cSetActiveFound(W8960_ADDR, "W8960-I2C", 1);

View File

@ -0,0 +1,270 @@
/*
audio is2 support pcm bridge
Copyright (C) 2022 Gerhard Mutz and Theo Arends
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef ESP32
#if (defined(USE_I2S_AUDIO) || defined(USE_TTGO_WATCH) || defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX) || defined(USE_I2S_MIC))
#ifdef I2S_BRIDGE
#ifndef I2S_BRIDGE_PORT
#define I2S_BRIDGE_PORT 6970
#endif
#define I2S_BRIDGE_BUFFER_SIZE 512
#define I2S_BRIDGE_MODE_OFF 0
#define I2S_BRIDGE_MODE_READ 1
#define I2S_BRIDGE_MODE_WRITE 2
void i2s_bridge_init(uint8_t mode) {
audio_i2s.bridge_mode.mode = mode;
if (I2S_BRIDGE_MODE_OFF == mode) {
audio_i2s.i2s_bridge_udp.flush();
audio_i2s.i2s_bridge_udp.stop();
//SpeakerMic(MODE_SPK);
AUDIO_PWR_OFF
} else {
i2s_set_clk(audio_i2s.mic_port, audio_i2s.mic_rate, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_STEREO);
if ((mode & 3) == I2S_BRIDGE_MODE_WRITE) {
//SpeakerMic(MODE_MIC);
//REG_SET_BIT(I2S_TIMING_REG(audio_i2s.mic_port), BIT(9));
//REG_SET_BIT(I2S_CONF_REG(audio_i2s.mic_port), I2S_RX_MSB_SHIFT);
} else {
//SpeakerMic(MODE_SPK);
}
audio_i2s.i2s_bridge_udp.begin(I2S_BRIDGE_PORT);
xTaskCreatePinnedToCore(i2s_bridge_task, "BRIDGE", 8192, NULL, 3, &audio_i2s.i2s_bridge_h, 1);
if (!audio_i2s.bridge_mode.master) {
AddLog(LOG_LEVEL_INFO, PSTR("I2S_bridge: slave started"));
} else {
char buffer[32];
sprintf_P(buffer, PSTR("%u.%u.%u.%u"), audio_i2s.i2s_bridge_ip[0], audio_i2s.i2s_bridge_ip[1], audio_i2s.i2s_bridge_ip[2], audio_i2s.i2s_bridge_ip[3]);
AddLog(LOG_LEVEL_INFO, PSTR("I2S_bridge: master started sending to ip: %s"), buffer);
}
AUDIO_PWR_ON
}
}
// make mono
void make_mono(int16_t *packet_buffer, uint32_t size) {
int16_t *wp = (int16_t*)packet_buffer;
for (uint32_t cnt = 0; cnt < size / 2; cnt += 2) {
int16_t val;
if (audio_i2s.bridge_mode.swap_mic) {
val = wp[cnt + 1] * audio_i2s.mic_gain;
} else {
val = wp[cnt] * audio_i2s.mic_gain;
}
wp[cnt] = val;
wp[cnt + 1] = val;
}
}
void i2s_bridge_task(void *arg) {
int16_t packet_buffer[I2S_BRIDGE_BUFFER_SIZE/2];
uint16_t bytesize;
while (I2S_BRIDGE_MODE_OFF != audio_i2s.bridge_mode.mode) {
if ((audio_i2s.bridge_mode.mode & 3) == 3) {
// loopback test mode
uint32_t bytes_read;
bytesize = I2S_BRIDGE_BUFFER_SIZE;
i2s_read(audio_i2s.mic_port, (char *)packet_buffer, bytesize, &bytes_read, (100 / portTICK_RATE_MS));
make_mono(packet_buffer, bytes_read);
uint32_t bytes_written;
i2s_write(audio_i2s.i2s_port, (const uint8_t*)packet_buffer, bytes_read, &bytes_written, 0);
} else {
if (audio_i2s.bridge_mode.mode & I2S_BRIDGE_MODE_READ) {
if (audio_i2s.i2s_bridge_udp.parsePacket()) {
uint32_t bytes_written;
uint32_t len = audio_i2s.i2s_bridge_udp.available();
if (len > I2S_BRIDGE_BUFFER_SIZE) {
len = I2S_BRIDGE_BUFFER_SIZE;
}
len = audio_i2s.i2s_bridge_udp.read((uint8_t *)packet_buffer, len);
audio_i2s.i2s_bridge_udp.flush();
i2s_write(audio_i2s.i2s_port, (const uint8_t*)packet_buffer, len, &bytes_written, 0);
} else {
delay(1);
}
}
if (audio_i2s.bridge_mode.mode & I2S_BRIDGE_MODE_WRITE) {
uint32_t bytes_read;
bytesize = I2S_BRIDGE_BUFFER_SIZE;
i2s_read(audio_i2s.mic_port, (char *)packet_buffer, bytesize, &bytes_read, (100 / portTICK_RATE_MS));
make_mono(packet_buffer, bytes_read);
audio_i2s.i2s_bridge_udp.beginPacket(audio_i2s.i2s_bridge_ip, I2S_BRIDGE_PORT);
audio_i2s.i2s_bridge_udp.write((const uint8_t*)packet_buffer, bytes_read);
audio_i2s.i2s_bridge_udp.endPacket();
}
}
}
AddLog(LOG_LEVEL_INFO, PSTR("I2S_bridge: stopped"));
vTaskDelete(NULL);
}
void Cmd_I2SBridge(void) {
if (XdrvMailbox.data_len > 0) {
char *cp = XdrvMailbox.data;
if (strchr(cp, '.')) {
// enter destination ip
audio_i2s.i2s_bridge_ip.fromString(cp);
Response_P(PSTR("{\"I2S_bridge\":{\"IP\":\"%s\"}}"), cp);
} else if (cp = strchr(cp, 'p')) {
// enter push to talk pin
cp++;
audio_i2s.ptt_pin = atoi(cp);
pinMode(audio_i2s.ptt_pin, INPUT_PULLUP);
Response_P(PSTR("{\"I2S_bridge\":{\"PTT-PIN\":%d}}"), audio_i2s.ptt_pin);
} else {
I2SBridgeCmd(XdrvMailbox.payload, 1);
}
}
}
void SendBridgeCmd(uint8_t mode) {
char slavecmd[16];
if (audio_i2s.bridge_mode.master) {
sprintf(slavecmd,"cmd:%d", mode);
audio_i2s.i2s_bridgec_udp.beginPacket(audio_i2s.i2s_bridge_ip, I2S_BRIDGE_PORT + 1);
audio_i2s.i2s_bridgec_udp.write((const uint8_t*)slavecmd,strlen(slavecmd));
audio_i2s.i2s_bridgec_udp.endPacket();
}
}
void I2SBridgeCmd(uint8_t val, uint8_t flg) {
if ((val >= 0) && (val <= 11)) {
if (val > 3) {
switch (val) {
case 4:
audio_i2s.bridge_mode.master = 1;
break;
case 5:
audio_i2s.bridge_mode.master = 0;
break;
case 6:
audio_i2s.bridge_mode.swap_mic = 1;
break;
case 7:
audio_i2s.bridge_mode.swap_mic = 0;
break;
}
Response_P(PSTR("{\"I2S_bridge\":{\"SWAP_MIC\":%d}}"), audio_i2s.bridge_mode.swap_mic);
} else {
if (audio_i2s.bridge_mode.mode != val) {
if ((val == I2S_BRIDGE_MODE_OFF) && (audio_i2s.bridge_mode.mode != I2S_BRIDGE_MODE_OFF)) {
if (flg && (audio_i2s.bridge_mode.master)) {
// shutdown slave
SendBridgeCmd(I2S_BRIDGE_MODE_OFF);
}
i2s_bridge_init(I2S_BRIDGE_MODE_OFF);
} else {
if (audio_i2s.bridge_mode.mode == I2S_BRIDGE_MODE_OFF) {
// initial on
i2s_bridge_init(val);
} else {
// change mode
if (val & I2S_BRIDGE_MODE_READ) {
//SpeakerMic(MODE_SPK);
}
if (val & I2S_BRIDGE_MODE_WRITE) {
//SpeakerMic(MODE_MIC);
}
}
}
audio_i2s.bridge_mode.mode = val;
if (flg) {
if (audio_i2s.bridge_mode.master) {
// set slave to complementary mode
if (audio_i2s.bridge_mode.mode && ((audio_i2s.bridge_mode.mode & 3) != 3)) {
uint8_t slavemode = I2S_BRIDGE_MODE_READ;
if (audio_i2s.bridge_mode.mode & I2S_BRIDGE_MODE_READ) {
slavemode = I2S_BRIDGE_MODE_WRITE;
}
SendBridgeCmd(slavemode);
}
}
}
}
ResponseCmndNumber(audio_i2s.bridge_mode.mode);
}
}
}
void i2s_bridge_loop(void) {
uint8_t packet_buffer[64];
if (TasmotaGlobal.global_state.wifi_down) {
return;
}
if (audio_i2s.ptt_pin >= 0) {
if (audio_i2s.bridge_mode.mode != I2S_BRIDGE_MODE_OFF) {
if (digitalRead(audio_i2s.ptt_pin) == 0) {
// push to talk
if (audio_i2s.bridge_mode.mode != I2S_BRIDGE_MODE_WRITE) {
I2SBridgeCmd(I2S_BRIDGE_MODE_WRITE, 1);
}
} else {
if (audio_i2s.bridge_mode.mode != I2S_BRIDGE_MODE_READ) {
I2SBridgeCmd(I2S_BRIDGE_MODE_READ, 1);
}
}
}
}
if (audio_i2s.i2s_bridgec_udp.parsePacket()) {
// received control command
memset(packet_buffer,0,sizeof(packet_buffer));
audio_i2s.i2s_bridgec_udp.read(packet_buffer, 63);
char *cp = (char*)packet_buffer;
if (!strncmp(cp, "cmd:", 4)) {
cp += 4;
I2SBridgeCmd(atoi(cp), 0);
audio_i2s.i2s_bridge_ip = audio_i2s.i2s_bridgec_udp.remoteIP();
AddLog(LOG_LEVEL_INFO, PSTR("I2S_bridge: remote cmd %d"), audio_i2s.bridge_mode.mode);
}
}
}
void I2SBridgeInit(void) {
// start udp control channel
audio_i2s.i2s_bridgec_udp.begin(I2S_BRIDGE_PORT + 1);
//audio_i2s.i2s_bridgec_udp.flush();
//audio_i2s.i2s_bridgec_udp.stop();
//I2SBridgeCmd(audio_i2s.bridge_mode.mode, 1);
AddLog(LOG_LEVEL_INFO, PSTR("I2S_bridge: command server created on port: %d "), I2S_BRIDGE_PORT + 1);
}
#endif // I2S_BRIDGE
#endif // USE_I2S_AUDIO
#endif // ESP32