From 7de25acac0a8ddd171dd6a745bde377fc2d5d4e1 Mon Sep 17 00:00:00 2001 From: s-hadinger <49731213+s-hadinger@users.noreply.github.com> Date: Thu, 21 Sep 2023 09:00:28 +0200 Subject: [PATCH] Minor refactoring of audio code for Arduino3 (#19559) --- ...e_i2s_audio_lib.cpp => be_i2s_audio_lib.c} | 123 ++---- .../xdrv_42_0_i2s__lib_idf51.ino | 350 ++++++++++++++++ .../xdrv_42_0_i2s_audio_idf51.ino | 387 ++---------------- .../xdrv_52_3_berry_audio.ino | 121 ++++-- 4 files changed, 504 insertions(+), 477 deletions(-) rename lib/libesp32/berry_tasmota/src/{be_i2s_audio_lib.cpp => be_i2s_audio_lib.c} (56%) create mode 100644 tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s__lib_idf51.ino diff --git a/lib/libesp32/berry_tasmota/src/be_i2s_audio_lib.cpp b/lib/libesp32/berry_tasmota/src/be_i2s_audio_lib.c similarity index 56% rename from lib/libesp32/berry_tasmota/src/be_i2s_audio_lib.cpp rename to lib/libesp32/berry_tasmota/src/be_i2s_audio_lib.c index 40322c08f..f347e13f4 100644 --- a/lib/libesp32/berry_tasmota/src/be_i2s_audio_lib.cpp +++ b/lib/libesp32/berry_tasmota/src/be_i2s_audio_lib.c @@ -9,134 +9,80 @@ #ifdef USE_I2S_AUDIO_BERRY #include "be_mapping.h" -#include "AudioOutput.h" -extern "C" void berry_log_C(const char * berry_buf, ...); +extern int i2s_output_i2s_init(bvm *vm); +extern int i2s_output_i2s_deinit(bvm *vm); +extern int i2s_output_i2s_stop(bvm *vm); -extern "C" { - extern int i2s_output_i2s_init(bvm *vm); - extern int i2s_output_i2s_deinit(bvm *vm); - extern int i2s_output_i2s_stop(bvm *vm); +extern int i2s_generator_wav_init(bvm *vm); +extern int i2s_generator_wav_deinit(bvm *vm); +extern int i2s_generator_wav_begin(bvm *vm); +extern int i2s_generator_wav_loop(bvm *vm); +extern int i2s_generator_wav_stop(bvm *vm); +extern int i2s_generator_wav_isrunning(bvm *vm); - extern int i2s_generator_wav_init(bvm *vm); - extern int i2s_generator_wav_deinit(bvm *vm); - extern int i2s_generator_wav_begin(bvm *vm); - extern int i2s_generator_wav_loop(bvm *vm); - extern int i2s_generator_wav_stop(bvm *vm); - extern int i2s_generator_wav_isrunning(bvm *vm); - - extern int i2s_generator_mp3_init(bvm *vm); - extern int i2s_generator_mp3_deinit(bvm *vm); - extern int i2s_generator_mp3_begin(bvm *vm); - extern int i2s_generator_mp3_loop(bvm *vm); - extern int i2s_generator_mp3_stop(bvm *vm); - extern int i2s_generator_mp3_isrunning(bvm *vm); +extern int i2s_generator_mp3_init(bvm *vm); +extern int i2s_generator_mp3_deinit(bvm *vm); +extern int i2s_generator_mp3_begin(bvm *vm); +extern int i2s_generator_mp3_loop(bvm *vm); +extern int i2s_generator_mp3_stop(bvm *vm); +extern int i2s_generator_mp3_isrunning(bvm *vm); #ifdef USE_UFILESYS - extern int i2s_file_source_fs_init(bvm *vm); - extern int i2s_file_source_fs_deinit(bvm *vm); +extern int i2s_file_source_fs_init(bvm *vm); +extern int i2s_file_source_fs_deinit(bvm *vm); #endif // USE_UFILESYS -} + // AudioOutput.set_rate(rate_hz:int) -> bool -AudioOutput* be_audio_output_init(void) { - return new AudioOutput(); -} +extern void* be_audio_output_init(void); BE_FUNC_CTYPE_DECLARE(be_audio_output_init, "+.p", ""); // AudioOutput.set_rate(rate_hz:int) -> bool -int be_audio_output_set_rate(AudioOutput* out, int hz) { - return out->SetRate(hz); -} +extern int be_audio_output_set_rate(void* out, int hz); BE_FUNC_CTYPE_DECLARE(be_audio_output_set_rate, "b", ".i"); // AudioOutput.set_bits_per_sample(bits_per_sample:int) -> bool -int be_audio_output_set_bits_per_sample(AudioOutput* out, int bps) { - return out->SetBitsPerSample(bps); -} +extern int be_audio_output_set_bits_per_sample(void* out, int bps); BE_FUNC_CTYPE_DECLARE(be_audio_output_set_bits_per_sample, "b", ".i"); // AudioOutput.set_channels(channels:int) -> bool -int be_audio_output_set_channels(AudioOutput* out, int channels) { - return out->SetChannels(channels); -} +extern int be_audio_output_set_channels(void* out, int channels); BE_FUNC_CTYPE_DECLARE(be_audio_output_set_channels, "b", ".i"); // AudioOutput.set_gain(gain:real) -> bool -int be_audio_output_set_gain(AudioOutput* out, float gain) { - return out->SetGain(gain); -} +extern int be_audio_output_set_gain(void* out, float gain); BE_FUNC_CTYPE_DECLARE(be_audio_output_set_gain, "b", ".f"); // AudioOutput.begin() -> bool -int be_audio_output_begin(AudioOutput* out) { - return out->begin(); -} +extern int be_audio_output_begin(void* out); BE_FUNC_CTYPE_DECLARE(be_audio_output_begin, "b", "."); // AudioOutput.stop() -> bool -int be_audio_output_stop(AudioOutput* out) { - return out->stop(); -} +extern int be_audio_output_stop(void* out); BE_FUNC_CTYPE_DECLARE(be_audio_output_stop, "b", "."); // AudioOutput.flush() -> bool -void be_audio_output_flush(AudioOutput* out) { - out->flush(); -} +extern void be_audio_output_flush(void* out); BE_FUNC_CTYPE_DECLARE(be_audio_output_flush, "", "."); // AudioOutput.consume_mono(bytes) -> int -int be_audio_output_consume_mono(AudioOutput* out, uint16_t *pcm, int bytes_len, int index) { - int pcm_len = bytes_len / 2; - int n; - // berry_log_C("be_audio_output_consume_mono_ntv out=%p pcm=%p bytes_len=%i index=%i", out, pcm, bytes_len, index); - for (n = 0; index + n < pcm_len; n++) { - int16_t ms[2]; - ms[AudioOutput::LEFTCHANNEL] = ms[AudioOutput::RIGHTCHANNEL] = pcm[index + n]; - if (!out->ConsumeSample(ms)) { break; } - } - return n; -} +extern int be_audio_output_consume_mono(void* out, uint16_t *pcm, int bytes_len, int index); BE_FUNC_CTYPE_DECLARE(be_audio_output_consume_mono, "i", ".(bytes)~i"); // AudioOutput.consume_stereo(bytes) -> int -int be_audio_output_consume_stereo(AudioOutput* out, uint16_t *pcm, int bytes_len, int index) { - int pcm_len = bytes_len / 4; // 2 samples LEFT+RIGHT of 2 bytes each - int n; - // berry_log_C("be_audio_output_consume_stereo_ntv out=%p pcm=%p bytes_len=%i index=%i", out, pcm, bytes_len, index); - for (n = 0; index + n < pcm_len; n++) { - int16_t ms[2]; - ms[AudioOutput::LEFTCHANNEL] = pcm[index + n + n]; - ms[AudioOutput::RIGHTCHANNEL] = pcm[index + n + n + 1]; - if (!out->ConsumeSample(ms)) { break; } - } - return n; -} +extern int be_audio_output_consume_stereo(void* out, uint16_t *pcm, int bytes_len, int index); BE_FUNC_CTYPE_DECLARE(be_audio_output_consume_stereo, "i", ".(bytes)~i"); // AudioOutput.consume_silence() -> int, push silence frames -int be_audio_output_consume_silence(AudioOutput* out) { - int n = 0; - int16_t ms[2] = {0, 0}; - while (true) { - if (!out->ConsumeSample(ms)) { break; } - n++; - } - return n; -} +extern int be_audio_output_consume_silence(void* out); BE_FUNC_CTYPE_DECLARE(be_audio_output_consume_silence, "i", "."); -#include "AudioOutputI2S.h" - // AudioOutputI2S.set_lsb_justified(gain:real) -> nil -int i2s_output_i2s_set_lsb_justified(AudioOutputI2S* out, bbool lsbJustified) { - return out->SetLsbJustified(lsbJustified); -} +extern int i2s_output_i2s_set_lsb_justified(void* out, bbool lsbJustified); BE_FUNC_CTYPE_DECLARE(i2s_output_i2s_set_lsb_justified, "b", ".b"); -extern "C" { - + #include "be_fixed_be_class_AudioOutput.h" #include "be_fixed_be_class_AudioOutputI2S.h" #include "be_fixed_be_class_AudioGenerator.h" @@ -145,7 +91,6 @@ extern "C" { #include "be_fixed_be_class_AudioFileSource.h" #include "be_fixed_be_class_AudioFileSourceFS.h" -} /* @const_object_info_begin class be_class_AudioOutput (scope: global, name: AudioOutput, strings: weak) { @@ -175,15 +120,9 @@ class be_class_AudioFileSource (scope: global, name: AudioFileSource, strings: w } class be_class_AudioOutputI2S (scope: global, name: AudioOutputI2S, super: be_class_AudioOutput, strings: weak) { - EXTERNAL_I2S, int(AudioOutputI2S::EXTERNAL_I2S) - INTERNAL_DAC, int(AudioOutputI2S::INTERNAL_DAC) - INTERNAL_PDM, int(AudioOutputI2S::INTERNAL_PDM) - init, func(i2s_output_i2s_init) deinit, func(i2s_output_i2s_deinit) stop, func(i2s_output_i2s_stop) - - set_lsb_justified, ctype_func(i2s_output_i2s_set_lsb_justified) } class be_class_AudioGeneratorWAV (scope: global, name: AudioGeneratorWAV, super: be_class_AudioGenerator, strings: weak) { diff --git a/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s__lib_idf51.ino b/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s__lib_idf51.ino new file mode 100644 index 000000000..5d6c6a0de --- /dev/null +++ b/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s__lib_idf51.ino @@ -0,0 +1,350 @@ +/* + xdrv_42_0_i2s__lib_idf51.ino - Simplified Audio library + + Copyright (C) 2021 Gerhard Mutz, Theo Arends, Staars, Stephan Hadinger + + 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 . +*/ + +#if defined(ESP32) && ESP_IDF_VERSION_MAJOR >= 5 +#ifdef USE_I2S_AUDIO + +#include "driver/i2s_std.h" +#include "driver/i2s_pdm.h" +#include "driver/gpio.h" + +#include "AudioFileSourcePROGMEM.h" +#include "AudioFileSourceID3.h" +#include "AudioGeneratorMP3.h" + +#include +#include "AudioFileSourceFS.h" +#include "AudioGeneratorTalkie.h" +#include "AudioFileSourceICYStream.h" +#include "AudioFileSourceBuffer.h" +#include "AudioGeneratorAAC.h" + +#include +#include + +#undef AUDIO_PWR_ON +#undef AUDIO_PWR_OFF +#define AUDIO_PWR_ON I2SAudioPower(true); +#define AUDIO_PWR_OFF I2SAudioPower(false); + +#define USE_I2S_SAY +#define USE_I2S_SAY_TIME +#define USE_I2S_RTTTL + +/*********************************************************************************************\ + * Driver Settings in memory +\*********************************************************************************************/ + +typedef struct{ + struct{ + uint32_t version : 8 = 0; + + // runtime options, will be saved but ignored on setting read + uint32_t duplex : 1 = 0; // depends on GPIO setting and SOC caps, DIN and DOUT on same port in GPIO means -> try to use duplex if possible + uint32_t tx : 1 = 0; // depends on GPIO setting + uint32_t rx : 1 = 0; // depends on GPIO setting + } sys; + struct { + uint32_t mode : 2 = 0; // bit 0+1 STD = 0, PDM = 1, TDM = 2 + uint32_t apll : 1 = 1; // bit 2 - will be ignored on unsupported SOC's + uint32_t mono : 1 = 0; // bit 3 0 = stereo, 1 = mono + uint32_t codec : 1 = 0; // bit 4 - S3 box only, unused for now + uint32_t webradio : 1 = 1; // bit 5 - allocate buffer for webradio + uint32_t spare01 : 1 = 1; // bit 6 - request duplex, means RX and TX on 1 slot + uint32_t lsbJustified : 1; // bit 7 - allow supporting LSBJ chips, e.g. TM8211/PT8211 + uint32_t volume : 8 = 10; // bit 8-15 + uint32_t spare02 : 16; // bit 16-31 + } tx; + struct { + struct{ + uint16_t sample_rate = 32000; + uint8_t gain = 30; + uint8_t mode = 0; //STD = 0, PDM = 1, TDM = 2 + + uint8_t slot_mask : 2 = 1; // left = 1 /right = 2 /both = 3 + uint8_t slot_mode : 1 = 0; // mono/stereo - 1 is added for both + uint8_t codec : 1 = 0; // unused for now + uint8_t mp3_encoder : 1 = 1; // will be ignored without PS-RAM + }; + } rx; +} tI2SSettings; + +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; + +class TasmotaAudioOutputI2S; + +struct AUDIO_I2S_t { + tI2SSettings *Settings; + + i2s_chan_handle_t rx_handle = nullptr; + + AudioGeneratorMP3 *mp3 = nullptr; + AudioFileSourceFS *file; + + TasmotaAudioOutputI2S *out; + + AudioFileSourceID3 *id3; + AudioGeneratorMP3 *decoder = NULL; + void *mp3ram = NULL; + + // Webradio + AudioFileSourceICYStream *ifile = NULL; + AudioFileSourceBuffer *buff = NULL; + char wr_title[64]; + void *preallocateBuffer = NULL; + void *preallocateCodec = NULL; + uint32_t retryms = 0; + + + TaskHandle_t mp3_task_handle; + TaskHandle_t mic_task_handle; + + uint32_t mic_size; + uint8_t *mic_buff; + char mic_path[32]; + File fwp; + uint8_t mic_stop; + int8_t mic_error; + bool use_stream = false; + + +// SHINE + uint32_t recdur; + uint8_t stream_active; + uint8_t stream_enable; + WiFiClient client; + ESP8266WebServer *MP3Server; + +// 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; + +} audio_i2s; + +extern FS *ufsp; +extern FS *ffsp; + +const int preallocateBufferSize = 16*1024; +const int preallocateCodecSize = 29192; // MP3 codec max mem needed +//const int preallocateCodecSize = 85332; // AAC+SBR codec max mem needed + +enum : int { EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2 }; + +void sayTime(int hour, int minutes); +void Cmndwav2mp3(void); +void Cmd_Time(void); + +void Rtttl(char *buffer); +void CmndI2SRtttl(void); + +/*********************************************************************************************\ + * Class for outputting sound as endpoint for ESP8266Audio library +\*********************************************************************************************/ + +class TasmotaAudioOutputI2S : public AudioOutput +{ +public: + + // Constructor takes no parameter, everything is configured from template and config file + TasmotaAudioOutputI2S() { + hertz = 16000; + i2sOn = false; + bps = I2S_DATA_BIT_WIDTH_16BIT; + mono = audio_i2s.Settings->tx.mono; + lsbJustified = audio_i2s.Settings->tx.lsbJustified; + channels = mono ? I2S_SLOT_MODE_MONO : I2S_SLOT_MODE_STEREO; + output_mode = EXTERNAL_I2S; + tx_is_enabled = false; + } + + ~TasmotaAudioOutputI2S() { + if(i2sOn){ + this->stop(); + i2s_del_channel(tx_chan); + } + } + + // ------------------------------------------------------------------------------------------ + // Setters for configuration parameters + // ------------------------------------------------------------------------------------------ + bool SetBitsPerSample(int bits) { + if ( (bits != 16) && (bits != 8) ) { return false; } + this->bps = bits; + return true; + } + + bool SetChannels(int channels) { + if ((channels < 1) || (channels > 2)) { return false; } + if (channels == (int)this->channels) { return true; } + this->channels = channels; + return true; + } + + bool SetRate(int hz) { + if (hz == (int)this->hertz) { return true; } + this->hertz = hz; + if(i2sOn){ + int result = updateClockConfig(); + AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: SetRate: %i - %i"),hz, result); + } + return true; + } + + bool SetPinout() { // TODO is this method still useful? + return this->startI2SChannel(); + } + + bool begin(void); + bool stop(void); + bool ConsumeSample(int16_t sample[2]); + bool startI2SChannel(void); + int updateClockConfig(void); + +protected: + enum : int { EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2 }; + int output_mode; + bool i2sOn; + bool mono; + bool lsbJustified; + + i2s_chan_handle_t tx_chan; + bool tx_is_enabled; + +}; + + +// ------------------------------------------------------------------------------------------ +// Methods +// ------------------------------------------------------------------------------------------ +bool TasmotaAudioOutputI2S::begin() { + if (tx_is_enabled) { return true; } + if (!i2sOn) { + if (audio_i2s.Settings->sys.duplex == 0 && audio_i2s.Settings->sys.rx == 1) { + this->startI2SChannel(); + } + } + int result = i2s_channel_enable(tx_chan); + if (result != 0){ + AddLog(LOG_LEVEL_INFO, "I2S: Could not enable i2s_channel: %i", result); + return false; + } + tx_is_enabled = true; + return true; +} + +bool TasmotaAudioOutputI2S::stop() { + i2s_channel_disable(tx_chan); + if(audio_i2s.Settings->sys.duplex == 0 && audio_i2s.Settings->sys.rx == 1){ + i2s_del_channel(tx_chan); + i2sOn = false; + } + tx_is_enabled = false; + return true; +} + +bool TasmotaAudioOutputI2S::ConsumeSample(int16_t sample[2]) { + if (!tx_is_enabled) { return false; } + + int16_t ms[2]; + ms[0] = sample[0]; + ms[1] = sample[1]; + MakeSampleStereo16(ms); + + if (this->mono) { + // Average the two samples and overwrite + int32_t ttl = ms[LEFTCHANNEL] + ms[RIGHTCHANNEL]; + ms[LEFTCHANNEL] = ms[RIGHTCHANNEL] = (ttl>>1) & 0xffff; + } + uint32_t s32; + if (output_mode == INTERNAL_DAC) { + int16_t l = Amplify(ms[LEFTCHANNEL]) + 0x8000; + int16_t r = Amplify(ms[RIGHTCHANNEL]) + 0x8000; + s32 = (r << 16) | (l & 0xffff); + } else { + s32 = ((Amplify(ms[RIGHTCHANNEL])) << 16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff); + } + + size_t i2s_bytes_written; + i2s_channel_write(tx_chan, (const void*)&s32, sizeof(uint32_t), &i2s_bytes_written, 0); + return i2s_bytes_written; +} + +bool TasmotaAudioOutputI2S::startI2SChannel(void) { + gpio_num_t _DIN = I2S_GPIO_UNUSED; + + i2s_chan_config_t tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER); + if (audio_i2s.Settings->sys.duplex == 1) { + _DIN = (gpio_num_t)Pin(GPIO_I2S_DIN); + i2s_new_channel(&tx_chan_cfg, &tx_chan, &audio_i2s.rx_handle); + } else{ + i2s_new_channel(&tx_chan_cfg, &tx_chan, NULL); + } + + i2s_std_config_t tx_std_cfg = { + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(hertz), + .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG((i2s_data_bit_width_t)bps, (i2s_slot_mode_t)channels), + .gpio_cfg = { + .mclk = (gpio_num_t)Pin(GPIO_I2S_MCLK), + .bclk = (gpio_num_t)Pin(GPIO_I2S_BCLK), + .ws = (gpio_num_t)Pin(GPIO_I2S_WS), + .dout = (gpio_num_t)Pin(GPIO_I2S_DOUT), + .din = _DIN, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false, + }, + }, + }; + i2sOn = (i2s_channel_init_std_mode(tx_chan, &tx_std_cfg) == 0); + AddLog(LOG_LEVEL_DEBUG, "I2S: TX channel with %i bit width on %i channels initialized", bps, channels); + if (audio_i2s.Settings->sys.duplex == 1) { + i2s_channel_init_std_mode(audio_i2s.rx_handle, &tx_std_cfg); + AddLog(LOG_LEVEL_DEBUG, "I2S: RX channel added in full duplex mode"); + } + return i2sOn; +} + +int TasmotaAudioOutputI2S::updateClockConfig(void) { + i2s_channel_disable(tx_chan); + i2s_std_clk_config_t clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(hertz); +#ifdef SOC_I2S_SUPPORTS_APLL + if(audio_i2s.Settings->tx.apll == 1){ + clk_cfg.clk_src = I2S_CLK_SRC_APLL; + } +#endif + int result = i2s_channel_reconfig_std_clock(tx_chan, &clk_cfg ); + if(tx_is_enabled) i2s_channel_enable(tx_chan); + return result; +} + +#endif // USE_I2S_AUDIO +#endif //ESP_IDF_VERSION_MAJOR >= 5 diff --git a/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_audio_idf51.ino b/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_audio_idf51.ino index 355928874..e31d6b40f 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_audio_idf51.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_audio_idf51.ino @@ -22,327 +22,52 @@ #define XDRV_42 42 -#include "driver/i2s_std.h" -#include "driver/i2s_pdm.h" -#include "driver/gpio.h" - -#include "AudioFileSourcePROGMEM.h" -#include "AudioFileSourceID3.h" -#include "AudioGeneratorMP3.h" - -#include -#include "AudioFileSourceFS.h" -#include "AudioGeneratorTalkie.h" -#include "AudioFileSourceICYStream.h" -#include "AudioFileSourceBuffer.h" -#include "AudioGeneratorAAC.h" - -#include -#include - -#undef AUDIO_PWR_ON -#undef AUDIO_PWR_OFF -#define AUDIO_PWR_ON I2SAudioPower(true); -#define AUDIO_PWR_OFF I2SAudioPower(false); - -#define USE_I2S_SAY -#define USE_I2S_SAY_TIME -#define USE_I2S_RTTTL - /*********************************************************************************************\ - * Driver Settings in memory + * Commands definitions \*********************************************************************************************/ -typedef struct{ - struct{ - uint32_t version : 8 = 0; - - // runtime options, will be saved but ignored on setting read - uint32_t duplex : 1 = 0; // depends on GPIO setting and SOC caps, DIN and DOUT on same port in GPIO means -> try to use duplex if possible - uint32_t tx : 1 = 0; // depends on GPIO setting - uint32_t rx : 1 = 0; // depends on GPIO setting - } sys; - struct { - uint32_t mode : 2 = 0; // bit 0+1 STD = 0, PDM = 1, TDM = 2 - uint32_t apll : 1 = 1; // bit 2 - will be ignored on unsupported SOC's - uint32_t mono : 1 = 0; // bit 3 0 = stereo, 1 = mono - uint32_t codec : 1 = 0; // bit 4 - S3 box only, unused for now - uint32_t webradio : 1 = 1; // bit 5 - allocate buffer for webradio - uint32_t spare01 : 1 = 1; // bit 6 - request duplex, means RX and TX on 1 slot - uint32_t lsbJustified : 1; // bit 7 - allow supporting LSBJ chips, e.g. TM8211/PT8211 - uint32_t volume : 8 = 10; // bit 8-15 - uint32_t spare02 : 16; // bit 16-31 - } tx; - struct { - struct{ - uint16_t sample_rate = 32000; - uint8_t gain = 30; - uint8_t mode = 0; //STD = 0, PDM = 1, TDM = 2 - - uint8_t slot_mask : 2 = 1; // left = 1 /right = 2 /both = 3 - uint8_t slot_mode : 1 = 0; // mono/stereo - 1 is added for both - uint8_t codec : 1 = 0; // unused for now - uint8_t mp3_encoder : 1 = 1; // will be ignored without PS-RAM - }; - } rx; -} tI2SSettings; - -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; - -class TasmotaAudioOutputI2S; - -struct AUDIO_I2S_t { - tI2SSettings *Settings; - - i2s_chan_handle_t rx_handle = nullptr; - - AudioGeneratorMP3 *mp3 = nullptr; - AudioFileSourceFS *file; - - TasmotaAudioOutputI2S *out; - - AudioFileSourceID3 *id3; - AudioGeneratorMP3 *decoder = NULL; - void *mp3ram = NULL; - - // Webradio - AudioFileSourceICYStream *ifile = NULL; - AudioFileSourceBuffer *buff = NULL; - char wr_title[64]; - void *preallocateBuffer = NULL; - void *preallocateCodec = NULL; - uint32_t retryms = 0; - - - TaskHandle_t mp3_task_handle; - TaskHandle_t mic_task_handle; - - uint32_t mic_size; - uint8_t *mic_buff; - char mic_path[32]; - File fwp; - uint8_t mic_stop; - int8_t mic_error; - bool use_stream = false; - - -// SHINE - uint32_t recdur; - uint8_t stream_active; - uint8_t stream_enable; - WiFiClient client; - ESP8266WebServer *MP3Server; - -// 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; - -} audio_i2s; - -extern FS *ufsp; -extern FS *ffsp; - -const int preallocateBufferSize = 16*1024; -const int preallocateCodecSize = 29192; // MP3 codec max mem needed -//const int preallocateCodecSize = 85332; // AAC+SBR codec max mem needed - -enum : int { EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2 }; - -void sayTime(int hour, int minutes); -void Cmndwav2mp3(void); -void Cmd_Time(void); - -void Rtttl(char *buffer); -void CmndI2SRtttl(void); - -/*********************************************************************************************\ - * Class for outputting sound as endpoint for ESP8266Audio library -\*********************************************************************************************/ - -class TasmotaAudioOutputI2S : public AudioOutput -{ - public: - TasmotaAudioOutputI2S(){ - hertz = 16000; - i2sOn = false; - bps = I2S_DATA_BIT_WIDTH_16BIT; - mono = (audio_i2s.Settings->tx.mono == 1); - channels = mono ? I2S_SLOT_MODE_MONO : I2S_SLOT_MODE_STEREO; - output_mode = EXTERNAL_I2S; - tx_is_enabled = false; - } - - ~TasmotaAudioOutputI2S(){ - if(i2sOn){ - this->stop(); - i2s_del_channel(tx_chan); - } - } - - bool SetBitsPerSample(int bits) - { - if ( (bits != 16) && (bits != 8) ) return false; - this->bps = bits; - return true; - } - - bool SetChannels(int channels) - { - if ( (channels < 1) || (channels > 2) ) return false; - if (channels == (int)this->channels) return true; - this->channels = channels; - return true; - } - - bool SetRate(int hz){ - if (hz == (int)this->hertz) return true; - this->hertz = hz; - if(i2sOn){ - int result = updateClockConfig(); - AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: SetRate: %i - %i"),hz, result); - } - return true; - } - - bool SetPinout(){ - return this->startI2SChannel(); - } - - bool begin(){ - if(tx_is_enabled) return true; - if(i2sOn == false){ - if(audio_i2s.Settings->sys.duplex == 0 && audio_i2s.Settings->sys.rx == 1){ - this->startI2SChannel(); - } - } - int result = i2s_channel_enable(tx_chan); - if(result != 0){ - AddLog(LOG_LEVEL_INFO,PSTR("I2S: Could not enable i2s_channel: %i"), result); - return false; - } - tx_is_enabled = true; - return true; - } - bool stop(){ - i2s_channel_disable(tx_chan); - if(audio_i2s.Settings->sys.duplex == 0 && audio_i2s.Settings->sys.rx == 1){ - i2s_del_channel(tx_chan); - i2sOn = false; - } - tx_is_enabled = false; - return true; - } - - bool ConsumeSample(int16_t sample[2]) - { - if (!tx_is_enabled) - return false; - - int16_t ms[2]; - - ms[0] = sample[0]; - ms[1] = sample[1]; - MakeSampleStereo16( ms ); - - if (this->mono) { - // Average the two samples and overwrite - int32_t ttl = ms[LEFTCHANNEL] + ms[RIGHTCHANNEL]; - ms[LEFTCHANNEL] = ms[RIGHTCHANNEL] = (ttl>>1) & 0xffff; - } - uint32_t s32; - if (output_mode == INTERNAL_DAC) - { - int16_t l = Amplify(ms[LEFTCHANNEL]) + 0x8000; - int16_t r = Amplify(ms[RIGHTCHANNEL]) + 0x8000; - s32 = (r << 16) | (l & 0xffff); - } - else - { - s32 = ((Amplify(ms[RIGHTCHANNEL])) << 16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff); - } - - size_t i2s_bytes_written; - i2s_channel_write(tx_chan, (const void*)&s32, sizeof(uint32_t), &i2s_bytes_written, 0); - return i2s_bytes_written; - } - - private: - enum : int { EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2 }; - int output_mode; - bool i2sOn; - bool mono; - - i2s_chan_handle_t tx_chan; - bool tx_is_enabled; - - bool startI2SChannel(){ - gpio_num_t _DIN = I2S_GPIO_UNUSED; - - i2s_chan_config_t tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER); - if(audio_i2s.Settings->sys.duplex == 1){ - _DIN = (gpio_num_t)Pin(GPIO_I2S_DIN); - i2s_new_channel(&tx_chan_cfg, &tx_chan, &audio_i2s.rx_handle); - } - else{ - i2s_new_channel(&tx_chan_cfg, &tx_chan, NULL); - } - - i2s_std_config_t tx_std_cfg = { - .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(hertz), - .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG((i2s_data_bit_width_t)bps, (i2s_slot_mode_t)channels), - .gpio_cfg = { - .mclk = (gpio_num_t)Pin(GPIO_I2S_MCLK), - .bclk = (gpio_num_t)Pin(GPIO_I2S_BCLK), - .ws = (gpio_num_t)Pin(GPIO_I2S_WS), - .dout = (gpio_num_t)Pin(GPIO_I2S_DOUT), - .din = _DIN, - .invert_flags = { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = false, - }, - }, - }; - i2sOn = (i2s_channel_init_std_mode(tx_chan, &tx_std_cfg) == 0); - AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: TX channel with %i bit width on %i channels initialized"),bps, channels); - if(audio_i2s.Settings->sys.duplex == 1){ - i2s_channel_init_std_mode(audio_i2s.rx_handle, &tx_std_cfg); - AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: RX channel added in full duplex mode")); - } - return i2sOn; - } - - int updateClockConfig(){ - i2s_channel_disable(tx_chan); - i2s_std_clk_config_t clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(hertz); -#ifdef SOC_I2S_SUPPORTS_APLL - if(audio_i2s.Settings->tx.apll == 1){ - clk_cfg.clk_src = I2S_CLK_SRC_APLL; - } +const char kI2SAudio_Commands[] PROGMEM = "I2S|" + "Gain|Play|WR|Rec|MGain|Stop" +#ifdef USE_I2S_SAY + "|Say" +#endif // USE_I2S_SAY +#ifdef USE_I2S_SAY_TIME + "|Time" +#endif // USE_I2S_SAY_TIME +#ifdef USE_I2S_RTTTL + "|Rtttl" #endif - int result = i2s_channel_reconfig_std_clock(tx_chan, &clk_cfg ); - if(tx_is_enabled) i2s_channel_enable(tx_chan); - return result; - } +#if defined(USE_SHINE) && defined(MP3_MIC_STREAM) + "|Stream" +#endif // MP3_MIC_STREAM +#ifdef I2S_BRIDGE + "|Bridge" +#endif // I2S_BRIDGE +; + +void (* const I2SAudio_Command[])(void) PROGMEM = { + &CmndI2SGain, &CmndI2SPlay, &CmndI2SWebRadio, &CmndI2SMicRec, &CmndI2SMicGain, &CmndI2SStop, +#ifdef USE_I2S_SAY + &CmndI2SSay, +#endif // USE_I2S_SAY +#ifdef USE_I2S_SAY_TIME + &Cmd_Time, +#endif // USE_I2S_SAY_TIME +#ifdef USE_I2S_RTTTL + &CmndI2SI2SRtttl, +#endif +#if defined(USE_SHINE) && defined(MP3_MIC_STREAM) + &CmndI2SMP3Stream, +#endif // MP3_MIC_STREAM +#ifdef I2S_BRIDGE + &CmndI2SI2SBridge, +#endif // I2S_BRIDGE }; /*********************************************************************************************\ * microphone related functions \*********************************************************************************************/ - uint32_t I2sMicInit(uint8_t enable) { esp_err_t err = ESP_OK; i2s_slot_mode_t slot_mode = (audio_i2s.Settings->rx.slot_mode == 0) ? I2S_SLOT_MODE_MONO : I2S_SLOT_MODE_STEREO; @@ -901,46 +626,6 @@ void Say(char *text) { * Commands \*********************************************************************************************/ -const char kI2SAudio_Commands[] PROGMEM = "I2S|" - "Gain|Play|WR|Rec|MGain|Stop" -#ifdef USE_I2S_SAY - "|Say" -#ifdef USE_I2S_SAY_TIME - "|Time" -#endif // USE_I2S_SAY_TIME -#endif // USE_I2S_SAY - -#ifdef USE_I2S_RTTTL - "|Rtttl" -#endif -#if defined(USE_SHINE) && defined(MP3_MIC_STREAM) - "|Stream" -#endif // MP3_MIC_STREAM -#ifdef I2S_BRIDGE - "|Bridge" -#endif // I2S_BRIDGE -; - -void (* const I2SAudio_Command[])(void) PROGMEM = { - &CmndI2SGain, &CmndI2SPlay, &CmndI2SWebRadio, &CmndI2SMicRec, &CmndI2SMicGain, &CmndI2SStop, -#ifdef USE_I2S_SAY - &CmndI2SSay, -#ifdef USE_I2S_SAY_TIME - &Cmd_Time, -#endif // USE_I2S_SAY_TIME -#endif // USE_I2S_SAY - -#ifdef USE_I2S_RTTTL - &CmndI2SI2SRtttl, -#endif -#if defined(USE_SHINE) && defined(MP3_MIC_STREAM) - &CmndI2SMP3Stream, -#endif // MP3_MIC_STREAM -#ifdef I2S_BRIDGE - &CmndI2SI2SBridge, -#endif // I2S_BRIDGE -}; - void CmndI2SStop(void) { I2sStopPlaying(); ResponseCmndDone(); diff --git a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_audio.ino b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_audio.ino index 06957bced..932da058f 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_audio.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_audio.ino @@ -22,15 +22,15 @@ #ifdef USE_I2S_AUDIO_BERRY -// #include "AudioFileSourceSPIFFS.h" -// #include "AudioFileSourceID3.h" -#include "AudioOutputI2S.h" #include "AudioGeneratorWAV.h" #include "AudioGeneratorMP3.h" #include "AudioFileSourceFS.h" #include +#if ESP_IDF_VERSION_MAJOR < 5 + #error "USE_I2S_AUDIO_BERRY is only supported for ESP-IDF 5.1 or later" +#endif /*********************************************************************************************\ * AudioOutput class @@ -38,44 +38,97 @@ \*********************************************************************************************/ extern "C" { + // AudioOutput.set_rate(rate_hz:int) -> bool + void* be_audio_output_init(void) { + return new AudioOutput(); + } + // AudioOutput.set_rate(rate_hz:int) -> bool + int be_audio_output_set_rate(AudioOutput* out, int hz) { + return out->SetRate(hz); + } + + // AudioOutput.set_bits_per_sample(bits_per_sample:int) -> bool + int be_audio_output_set_bits_per_sample(AudioOutput* out, int bps) { + return out->SetBitsPerSample(bps); + } + + // AudioOutput.set_channels(channels:int) -> bool + int be_audio_output_set_channels(AudioOutput* out, int channels) { + return out->SetChannels(channels); + } + + // AudioOutput.set_gain(gain:real) -> bool + int be_audio_output_set_gain(AudioOutput* out, float gain) { + return out->SetGain(gain); + } + + // AudioOutput.begin() -> bool + int be_audio_output_begin(AudioOutput* out) { + return out->begin(); + } + + // AudioOutput.stop() -> bool + int be_audio_output_stop(AudioOutput* out) { + return out->stop(); + } + + // AudioOutput.flush() -> bool + void be_audio_output_flush(AudioOutput* out) { + out->flush(); + } + + // AudioOutput.consume_mono(bytes) -> int + int be_audio_output_consume_mono(AudioOutput* out, uint16_t *pcm, int bytes_len, int index) { + int pcm_len = bytes_len / 2; + int n; + // berry_log_C("be_audio_output_consume_mono_ntv out=%p pcm=%p bytes_len=%i index=%i", out, pcm, bytes_len, index); + for (n = 0; index + n < pcm_len; n++) { + int16_t ms[2]; + ms[AudioOutput::LEFTCHANNEL] = ms[AudioOutput::RIGHTCHANNEL] = pcm[index + n]; + if (!out->ConsumeSample(ms)) { break; } + } + return n; + } + + // AudioOutput.consume_stereo(bytes) -> int + int be_audio_output_consume_stereo(AudioOutput* out, uint16_t *pcm, int bytes_len, int index) { + int pcm_len = bytes_len / 4; // 2 samples LEFT+RIGHT of 2 bytes each + int n; + // berry_log_C("be_audio_output_consume_stereo_ntv out=%p pcm=%p bytes_len=%i index=%i", out, pcm, bytes_len, index); + for (n = 0; index + n < pcm_len; n++) { + int16_t ms[2]; + ms[AudioOutput::LEFTCHANNEL] = pcm[index + n + n]; + ms[AudioOutput::RIGHTCHANNEL] = pcm[index + n + n + 1]; + if (!out->ConsumeSample(ms)) { break; } + } + return n; + } + + // AudioOutput.consume_silence() -> int, push silence frames + int be_audio_output_consume_silence(AudioOutput* out) { + int n = 0; + int16_t ms[2] = {0, 0}; + while (true) { + if (!out->ConsumeSample(ms)) { break; } + n++; + } + return n; + } + // - // AudioOutputI2S(bclkPin: int, wclkPin: int, doutPin: int[, port:int, dmabuf:int, mode: int]) + // AudioOutputI2S() // int i2s_output_i2s_init(bvm *vm) { - int argc = be_top(vm); - if (argc > 3) { - int bclkPin = be_toint(vm, 2); - int wclkPin = be_toint(vm, 3); - int doutPin = be_toint(vm, 4); - int port = 0; - if (argc > 4) { - port = be_toint(vm, 5); - } - int dma_buf_count = 8; // number of dma buffers of 64 bytes - if (argc > 5) { - dma_buf_count = be_toint(vm, 6); - } - int mode = 0; // EXTERNAL_I2S - if (argc > 6) { - mode = be_toint(vm, 7); - } - // AudioOutputI2S(int port=0, int output_mode=EXTERNAL_I2S, int dma_buf_count = 8, int use_apll=APLL_DISABLE); - AudioOutputI2S * audio = new AudioOutputI2S(port, mode, dma_buf_count); - if (0 == mode) { - audio->SetPinout(bclkPin, wclkPin, doutPin); // return value has no useful information for us - } - be_pushcomptr(vm, (void*) audio); - be_setmember(vm, 1, ".p"); - be_return_nil(vm); - } - - be_raise(vm, kTypeError, nullptr); + TasmotaAudioOutputI2S * audio = new TasmotaAudioOutputI2S(); + be_pushcomptr(vm, (void*) audio); + be_setmember(vm, 1, ".p"); + be_return_nil(vm); } int i2s_output_i2s_deinit(bvm *vm) { int argc = be_top(vm); be_getmember(vm, 1, ".p"); - AudioOutputI2S * audio = (AudioOutputI2S *) be_tocomptr(vm, -1); + TasmotaAudioOutputI2S * audio = (TasmotaAudioOutputI2S *) be_tocomptr(vm, -1); if (audio) { delete audio; // clear @@ -89,7 +142,7 @@ extern "C" { int i2s_output_i2s_stop(bvm *vm) { int argc = be_top(vm); be_getmember(vm, 1, ".p"); - AudioOutputI2S * audio = (AudioOutputI2S *) be_tocomptr(vm, -1); + TasmotaAudioOutputI2S * audio = (TasmotaAudioOutputI2S *) be_tocomptr(vm, -1); if (audio) { audio->stop(); }