mirror of https://github.com/arendst/Tasmota.git
More cleaning of audio for ESP32 (#19577)
This commit is contained in:
parent
63fd3e753a
commit
375c825d32
|
@ -132,6 +132,13 @@ class AXP192_M5Stack_Core2 : AXP192
|
|||
end
|
||||
end
|
||||
|
||||
# respond to audio events
|
||||
def audio(cmd, idx, payload, raw)
|
||||
if cmd == "power"
|
||||
self.set_speaker_enable(idx)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return AXP192_M5Stack_Core2()
|
||||
|
|
|
@ -142,6 +142,13 @@ class AXP192_M5Stack_Tough : AXP192
|
|||
end
|
||||
end
|
||||
|
||||
# respond to audio events
|
||||
def audio(cmd, idx, payload, raw)
|
||||
if cmd == "power"
|
||||
self.set_speaker_enable(idx)
|
||||
end
|
||||
end
|
||||
|
||||
# Touch Panel reset pin
|
||||
def touch_reset(state)
|
||||
self.write_gpio(1, state)
|
||||
|
|
|
@ -38,15 +38,6 @@
|
|||
#include <layer3.h>
|
||||
#include <types.h>
|
||||
|
||||
#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
|
||||
\*********************************************************************************************/
|
||||
|
@ -65,11 +56,12 @@ typedef struct{
|
|||
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 slot_config : 3 = 0; // bit 5-7 - slot configuration MSB = 0, PCM = 1, PHILIPS = 2
|
||||
uint32_t volume : 8 = 10; // bit 8-15
|
||||
uint32_t spare02 : 16; // bit 16-31
|
||||
uint32_t mclk_inv : 1 = 0; // bit 16 - invert mclk
|
||||
uint32_t bclk_inv : 1 = 0; // bit 17 - invert bclk
|
||||
uint32_t ws_inv : 1 = 0; // bit 18 - invert ws
|
||||
uint32_t spare02 : 13; // bit 19-31
|
||||
} tx;
|
||||
struct {
|
||||
struct{
|
||||
|
@ -103,23 +95,14 @@ struct AUDIO_I2S_t {
|
|||
i2s_chan_handle_t rx_handle = nullptr;
|
||||
|
||||
AudioGeneratorMP3 *mp3 = nullptr;
|
||||
AudioFileSourceFS *file;
|
||||
AudioFileSourceFS *file = nullptr;
|
||||
|
||||
TasmotaAudioOutputI2S *out;
|
||||
TasmotaAudioOutputI2S *out = nullptr;
|
||||
|
||||
AudioFileSourceID3 *id3;
|
||||
AudioFileSourceID3 *id3 = nullptr;
|
||||
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;
|
||||
|
||||
|
@ -149,22 +132,8 @@ struct AUDIO_I2S_t {
|
|||
|
||||
} 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
|
||||
\*********************************************************************************************/
|
||||
|
@ -175,68 +144,105 @@ 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;
|
||||
loadSettings();
|
||||
}
|
||||
|
||||
~TasmotaAudioOutputI2S() {
|
||||
if(i2sOn){
|
||||
this->stop();
|
||||
i2s_del_channel(tx_chan);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Setters for configuration parameters
|
||||
//
|
||||
// TODO: not sure we still need them since all this should be set at initialiation
|
||||
// ------------------------------------------------------------------------------------------
|
||||
bool SetBitsPerSample(int bits) {
|
||||
virtual bool SetBitsPerSample(int bits) {
|
||||
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: SetBitsPerSample: %i"), bits);
|
||||
if ( (bits != 16) && (bits != 8) ) { return false; }
|
||||
this->bps = bits;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SetChannels(int channels) {
|
||||
virtual bool SetChannels(int channels) {
|
||||
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: SetChannels: %i"), 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; }
|
||||
virtual bool SetRate(int hz) {
|
||||
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: SetRate: %i"), hz);
|
||||
if (hz == (int) this->hertz) { return true; }
|
||||
this->hertz = hz;
|
||||
if(i2sOn){
|
||||
if (_i2s_on) {
|
||||
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();
|
||||
virtual bool SetGain(float f) {
|
||||
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: SetGain: %_f"), &f);
|
||||
return AudioOutput::SetGain(f);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Getters
|
||||
inline uint8_t getTxMode(void) const { return _tx_mode; }
|
||||
inline bool getTxMono(void) const { return _tx_mono; }
|
||||
inline bool getTxEnabled(void) const { return _tx_enabled; }
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Setters
|
||||
inline void setTxMode(uint8_t mode) { _tx_mode = mode; }
|
||||
inline void setTxMono(bool mono) { _tx_mono = mono; }
|
||||
inline void setTxEnabled(bool enabled) { _tx_enabled = enabled; }
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// AudioOutput has inconsistent case for methods, provide consistent setters for super class
|
||||
inline void setRate(int hz) { SetRate(hz); }
|
||||
inline void setBitsPerSample(int bits) { SetBitsPerSample(bits); }
|
||||
inline void setChannels(int chan) { SetChannels(chan); }
|
||||
inline void setGain(float f) { SetGain(f); }
|
||||
|
||||
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;
|
||||
// The following is now the preferred function
|
||||
// and allows to send multiple samples at once
|
||||
//
|
||||
// Max 128 samples, it is clipped otherwise
|
||||
// Returns: the number of samples actually consumed
|
||||
// or -1 if an error occured
|
||||
//
|
||||
// The call is non blocking and does not wait
|
||||
int32_t consumeSamples(int16_t *samples, size_t count);
|
||||
|
||||
i2s_chan_handle_t tx_chan;
|
||||
bool tx_is_enabled;
|
||||
protected:
|
||||
void loadSettings(void); // load all settings from Settings file and template - the goal is to have zero touch
|
||||
|
||||
protected:
|
||||
|
||||
bool _i2s_on = false; // is I2S audio active
|
||||
// local copy of useful settings for audio
|
||||
// TX
|
||||
uint8_t _tx_mode = EXTERNAL_I2S; // EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2
|
||||
bool _tx_enabled = false; // true = enabled, false = disabled
|
||||
bool _tx_mono = false; // true = mono, false = stereo
|
||||
i2s_chan_handle_t _tx_chan; // I2S channel handle, automatically computed
|
||||
|
||||
// GPIOs for I2S
|
||||
gpio_num_t _gpio_mclk = GPIO_NUM_NC; // GPIO for master clock
|
||||
gpio_num_t _gpio_bclk = GPIO_NUM_NC; // GPIO for bit clock
|
||||
gpio_num_t _gpio_ws = GPIO_NUM_NC; // GPIO for word select
|
||||
gpio_num_t _gpio_dout = GPIO_NUM_NC; // GPIO for data out
|
||||
gpio_num_t _gpio_din = GPIO_NUM_NC; // GPIO for data in
|
||||
bool _gpio_mclk_inv = false; // invert master clock
|
||||
bool _gpio_bclk_inv = false; // invert bit clock
|
||||
bool _gpio_ws_inv = false; // invert word select
|
||||
|
||||
};
|
||||
|
||||
|
@ -244,107 +250,160 @@ protected:
|
|||
// ------------------------------------------------------------------------------------------
|
||||
// Methods
|
||||
// ------------------------------------------------------------------------------------------
|
||||
void TasmotaAudioOutputI2S::loadSettings(void) {
|
||||
hertz = 16000;
|
||||
_i2s_on = false;
|
||||
bps = I2S_DATA_BIT_WIDTH_16BIT;
|
||||
_tx_mono = audio_i2s.Settings->tx.mono;
|
||||
channels = _tx_mono ? I2S_SLOT_MODE_MONO : I2S_SLOT_MODE_STEREO;
|
||||
_tx_mode = EXTERNAL_I2S;
|
||||
_tx_enabled = false;
|
||||
|
||||
_gpio_mclk = (gpio_num_t) Pin(GPIO_I2S_MCLK);
|
||||
_gpio_bclk = (gpio_num_t) Pin(GPIO_I2S_BCLK);
|
||||
_gpio_ws = (gpio_num_t) Pin(GPIO_I2S_WS);
|
||||
_gpio_dout = (gpio_num_t) Pin(GPIO_I2S_DOUT);
|
||||
_gpio_din = (gpio_num_t) Pin(GPIO_I2S_DIN);
|
||||
_gpio_mclk_inv = audio_i2s.Settings->tx.mclk_inv;
|
||||
_gpio_bclk_inv = audio_i2s.Settings->tx.bclk_inv;
|
||||
_gpio_ws_inv = audio_i2s.Settings->tx.ws_inv;
|
||||
}
|
||||
|
||||
bool TasmotaAudioOutputI2S::begin() {
|
||||
if (tx_is_enabled) { return true; }
|
||||
if (!i2sOn) {
|
||||
if (_tx_enabled) { return true; }
|
||||
if (!_i2s_on) {
|
||||
if (audio_i2s.Settings->sys.duplex == 0 && audio_i2s.Settings->sys.rx == 1) {
|
||||
this->startI2SChannel();
|
||||
}
|
||||
}
|
||||
int result = i2s_channel_enable(tx_chan);
|
||||
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;
|
||||
_tx_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;
|
||||
i2s_channel_disable(_tx_chan);
|
||||
if (audio_i2s.Settings->sys.duplex == 0 && audio_i2s.Settings->sys.rx == 1) {
|
||||
i2s_del_channel(_tx_chan);
|
||||
_i2s_on = false;
|
||||
}
|
||||
tx_is_enabled = false;
|
||||
_tx_enabled = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TasmotaAudioOutputI2S::ConsumeSample(int16_t sample[2]) {
|
||||
if (!tx_is_enabled) { return false; }
|
||||
return consumeSamples(sample, 1);
|
||||
}
|
||||
|
||||
int16_t ms[2];
|
||||
ms[0] = sample[0];
|
||||
ms[1] = sample[1];
|
||||
MakeSampleStereo16(ms);
|
||||
int32_t TasmotaAudioOutputI2S::consumeSamples(int16_t *samples, size_t count) {
|
||||
if (!_tx_enabled) { return -1; }
|
||||
if (count == 0) { return 0; }
|
||||
if (count > 128) { count = 128; }
|
||||
|
||||
if (this->mono) {
|
||||
int16_t ms[count*2];
|
||||
for (int32_t i = 0; i < count; i++) {
|
||||
int16_t left = samples[i*2 + LEFTCHANNEL];
|
||||
int16_t right = samples[i*2 + RIGHTCHANNEL];
|
||||
|
||||
if (this->_tx_mono) {
|
||||
// Average the two samples and overwrite
|
||||
int32_t ttl = ms[LEFTCHANNEL] + ms[RIGHTCHANNEL];
|
||||
ms[LEFTCHANNEL] = ms[RIGHTCHANNEL] = (ttl>>1) & 0xffff;
|
||||
int32_t ttl = left + right;
|
||||
left = right = (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);
|
||||
|
||||
if (bps == 8) {
|
||||
left = (((int16_t)(left & 0xff)) - 128) << 8;
|
||||
right = (((int16_t)(right & 0xff)) - 128) << 8;
|
||||
}
|
||||
|
||||
if (_tx_mode == INTERNAL_DAC) {
|
||||
left = Amplify(left) + 0x8000;
|
||||
right = Amplify(right) + 0x8000;
|
||||
} else {
|
||||
s32 = ((Amplify(ms[RIGHTCHANNEL])) << 16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff);
|
||||
left = Amplify(left);
|
||||
right = Amplify(right);
|
||||
}
|
||||
|
||||
ms[i*2 + LEFTCHANNEL] = left;
|
||||
ms[i*2 + RIGHTCHANNEL] = right;
|
||||
}
|
||||
|
||||
size_t i2s_bytes_written;
|
||||
i2s_channel_write(tx_chan, (const void*)&s32, sizeof(uint32_t), &i2s_bytes_written, 0);
|
||||
esp_err_t err = i2s_channel_write(_tx_chan, ms, sizeof(ms), &i2s_bytes_written, 0);
|
||||
if (err && err != ESP_ERR_TIMEOUT) {
|
||||
AddLog(LOG_LEVEL_INFO, "I2S: Could not write samples (count=%i): %i", count, err);
|
||||
return -1;
|
||||
}
|
||||
return i2s_bytes_written;
|
||||
}
|
||||
|
||||
// Initialize I2S channel
|
||||
// return `true` if successful, `false` otherwise
|
||||
bool TasmotaAudioOutputI2S::startI2SChannel(void) {
|
||||
gpio_num_t _DIN = I2S_GPIO_UNUSED;
|
||||
gpio_num_t _DIN = I2S_GPIO_UNUSED; // no input pin by default
|
||||
|
||||
// default dma_desc_num = 6 (DMA buffers), dma_frame_num = 240 (frames per buffer)
|
||||
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);
|
||||
_DIN = (gpio_num_t)_gpio_din;
|
||||
i2s_new_channel(&tx_chan_cfg, &_tx_chan, &audio_i2s.rx_handle);
|
||||
} else{
|
||||
i2s_new_channel(&tx_chan_cfg, &tx_chan, NULL);
|
||||
i2s_new_channel(&tx_chan_cfg, &_tx_chan, NULL);
|
||||
}
|
||||
|
||||
// by default we configure for MSB 2 slots `I2S_STD_MSB_SLOT_DEFAULT_CONFIG`
|
||||
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),
|
||||
.mclk = _gpio_mclk,
|
||||
.bclk = _gpio_bclk,
|
||||
.ws = _gpio_ws,
|
||||
.dout = _gpio_dout,
|
||||
.din = _DIN,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
.ws_inv = false,
|
||||
},
|
||||
},
|
||||
.mclk_inv = _gpio_mclk_inv,
|
||||
.bclk_inv = _gpio_bclk_inv,
|
||||
.ws_inv = _gpio_ws_inv,
|
||||
}
|
||||
}
|
||||
};
|
||||
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);
|
||||
|
||||
// change configuration if we are using PCM or PHILIPS
|
||||
if (audio_i2s.Settings->tx.slot_config == 1) { // PCM
|
||||
tx_std_cfg.slot_cfg = I2S_STD_PCM_SLOT_DEFAULT_CONFIG((i2s_data_bit_width_t)bps, (i2s_slot_mode_t)channels);
|
||||
} else if (audio_i2s.Settings->tx.slot_config == 2) { // PHILIPS
|
||||
tx_std_cfg.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG((i2s_data_bit_width_t)bps, (i2s_slot_mode_t)channels);
|
||||
}
|
||||
|
||||
_i2s_on = (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 ok=%i", bps, channels, _i2s_on);
|
||||
|
||||
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;
|
||||
return _i2s_on;
|
||||
}
|
||||
|
||||
int TasmotaAudioOutputI2S::updateClockConfig(void) {
|
||||
i2s_channel_disable(tx_chan);
|
||||
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){
|
||||
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);
|
||||
int result = i2s_channel_reconfig_std_clock(_tx_chan, &clk_cfg );
|
||||
if (_tx_enabled) { i2s_channel_enable(_tx_chan); }
|
||||
AddLog(LOG_LEVEL_DEBUG, "I2S: Updating clock config, result=%i", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif // USE_I2S_AUDIO
|
||||
#endif //ESP_IDF_VERSION_MAJOR >= 5
|
||||
#endif // defined(ESP32) && ESP_IDF_VERSION_MAJOR >= 5
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#if ESP_IDF_VERSION_MAJOR < 5
|
||||
#if ESP8266 || (ESP_IDF_VERSION_MAJOR < 5)
|
||||
#if (defined(USE_I2S_AUDIO) || defined(USE_TTGO_WATCH) || defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX))
|
||||
/*********************************************************************************************\
|
||||
* I2S support using an external DAC or a speaker connected to GPIO03 using a transistor
|
||||
|
@ -765,4 +765,4 @@ bool Xdrv42(uint32_t function) {
|
|||
}
|
||||
|
||||
#endif // USE_I2S_AUDIO
|
||||
#endif // ESP_IDF_VERSION_MAJOR < 5
|
||||
#endif // ESP8266 || (ESP_IDF_VERSION_MAJOR < 5)
|
||||
|
|
|
@ -17,17 +17,46 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||
#if defined(ESP32) && ESP_IDF_VERSION_MAJOR >= 5
|
||||
#if defined(USE_I2S_AUDIO)
|
||||
|
||||
#define XDRV_42 42
|
||||
|
||||
#define USE_I2S_SAY
|
||||
#define USE_I2S_SAY_TIME
|
||||
#define USE_I2S_RTTTL
|
||||
#define USE_I2S_WEBRADIO
|
||||
|
||||
// Macros used in audio sub-functions
|
||||
#undef AUDIO_PWR_ON
|
||||
#undef AUDIO_PWR_OFF
|
||||
#define AUDIO_PWR_ON I2SAudioPower(true);
|
||||
#define AUDIO_PWR_OFF I2SAudioPower(false);
|
||||
|
||||
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
|
||||
|
||||
void sayTime(int hour, int minutes);
|
||||
void Cmndwav2mp3(void);
|
||||
void Cmd_Time(void);
|
||||
|
||||
void Rtttl(char *buffer);
|
||||
void CmndI2SRtttl(void);
|
||||
void I2sWebRadioStopPlaying(void);
|
||||
|
||||
/*********************************************************************************************\
|
||||
* Commands definitions
|
||||
\*********************************************************************************************/
|
||||
|
||||
const char kI2SAudio_Commands[] PROGMEM = "I2S|"
|
||||
"Gain|Play|WR|Rec|MGain|Stop"
|
||||
"Gain|Play|Rec|MGain|Stop"
|
||||
#ifdef USE_I2S_WEBRADIO
|
||||
"|WR"
|
||||
#endif // USE_I2S_WEBRADIO
|
||||
#ifdef USE_I2S_SAY
|
||||
"|Say"
|
||||
#endif // USE_I2S_SAY
|
||||
|
@ -46,7 +75,10 @@ const char kI2SAudio_Commands[] PROGMEM = "I2S|"
|
|||
;
|
||||
|
||||
void (* const I2SAudio_Command[])(void) PROGMEM = {
|
||||
&CmndI2SGain, &CmndI2SPlay, &CmndI2SWebRadio, &CmndI2SMicRec, &CmndI2SMicGain, &CmndI2SStop,
|
||||
&CmndI2SGain, &CmndI2SPlay, &CmndI2SMicRec, &CmndI2SMicGain, &CmndI2SStop,
|
||||
#ifdef USE_I2S_WEBRADIO
|
||||
&CmndI2SWebRadio,
|
||||
#endif // USE_I2S_WEBRADIO
|
||||
#ifdef USE_I2S_SAY
|
||||
&CmndI2SSay,
|
||||
#endif // USE_I2S_SAY
|
||||
|
@ -378,7 +410,6 @@ void I2sCheckCfg(void){
|
|||
}
|
||||
else{
|
||||
audio_i2s.Settings->sys.tx = 0;
|
||||
audio_i2s.Settings->tx.webradio = 0; // do not allocate buffer
|
||||
}
|
||||
|
||||
AddLog(LOG_LEVEL_INFO, PSTR("I2S: init pins bclk=%d, ws=%d, dout=%d, mclk=%d, din=%d"), Pin(GPIO_I2S_BCLK) , Pin(GPIO_I2S_WS), Pin(GPIO_I2S_DOUT), Pin(GPIO_I2S_MCLK), Pin(GPIO_I2S_DIN));
|
||||
|
@ -401,7 +432,7 @@ bool I2sPinInit(void) {
|
|||
|
||||
if(audio_i2s.Settings->sys.tx == 1){
|
||||
audio_i2s.out = new TasmotaAudioOutputI2S;
|
||||
int err = audio_i2s.out->SetPinout() ? 0 : 1;
|
||||
int err = audio_i2s.out->startI2SChannel() ? 0 : 1;
|
||||
result += err;
|
||||
}
|
||||
|
||||
|
@ -431,17 +462,6 @@ void I2sInit(void) {
|
|||
audio_i2s.Settings->rx.mp3_encoder = 0; // no PS-RAM -> no MP3 encoding
|
||||
}
|
||||
}
|
||||
|
||||
if(audio_i2s.Settings->tx.webradio == 1){
|
||||
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: will allocate buffer for webradio "));
|
||||
if (UsePSRAM()) {
|
||||
audio_i2s.preallocateBuffer = heap_caps_malloc(preallocateBufferSize, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||
audio_i2s.preallocateCodec = heap_caps_malloc(preallocateCodecSize, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||
} else {
|
||||
audio_i2s.preallocateBuffer = malloc(preallocateBufferSize);
|
||||
audio_i2s.preallocateCodec = malloc(preallocateCodecSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*********************************************************************************************\
|
||||
|
@ -466,19 +486,6 @@ void I2sMp3Task(void *arg) {
|
|||
}
|
||||
}
|
||||
|
||||
void I2sMDCallback(void *cbData, const char *type, bool isUnicode, const char *str) {
|
||||
const char *ptr = reinterpret_cast<const char *>(cbData);
|
||||
(void) isUnicode; // Punt this ball for now
|
||||
(void) ptr;
|
||||
if (strstr_P(type, PSTR("Title"))) {
|
||||
strncpy(audio_i2s.wr_title, str, sizeof(audio_i2s.wr_title));
|
||||
audio_i2s.wr_title[sizeof(audio_i2s.wr_title)-1] = 0;
|
||||
//AddLog(LOG_LEVEL_INFO,PSTR("WR-Title: %s"),wr_title);
|
||||
} else {
|
||||
// Who knows what to do? Not me!
|
||||
}
|
||||
}
|
||||
|
||||
void I2sStatusCallback(void *cbData, int code, const char *string) {
|
||||
const char *ptr = reinterpret_cast<const char *>(cbData);
|
||||
(void) code;
|
||||
|
@ -487,29 +494,6 @@ void I2sStatusCallback(void *cbData, int code, const char *string) {
|
|||
//status[sizeof(status)-1] = 0;
|
||||
}
|
||||
|
||||
void Webradio(const char *url) {
|
||||
if (audio_i2s.decoder || audio_i2s.mp3) return;
|
||||
if (!audio_i2s.out) return;
|
||||
if (audio_i2s.Settings->tx.webradio == 0) return;
|
||||
I2SAudioPower(true);
|
||||
audio_i2s.ifile = new AudioFileSourceICYStream(url);
|
||||
audio_i2s.ifile->RegisterMetadataCB(I2sMDCallback, NULL);
|
||||
audio_i2s.buff = new AudioFileSourceBuffer(audio_i2s.ifile, audio_i2s.preallocateBuffer, preallocateBufferSize);
|
||||
audio_i2s.buff->RegisterStatusCB(I2sStatusCallback, NULL);
|
||||
audio_i2s.decoder = new AudioGeneratorMP3(audio_i2s.preallocateCodec, preallocateCodecSize);
|
||||
audio_i2s.decoder->RegisterStatusCB(I2sStatusCallback, NULL);
|
||||
audio_i2s.decoder->begin(audio_i2s.buff, audio_i2s.out);
|
||||
if (!audio_i2s.decoder->isRunning()) {
|
||||
// Serial.printf_P(PSTR("Can't connect to URL"));
|
||||
I2sStopPlaying();
|
||||
// strcpy_P(status, PSTR("Unable to connect to URL"));
|
||||
audio_i2s.retryms = millis() + 2000;
|
||||
}
|
||||
|
||||
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: will launch webradio task"));
|
||||
xTaskCreatePinnedToCore(I2sMp3Task2, "MP3-2", 8192, NULL, 3, &audio_i2s.mp3_task_handle, 1);
|
||||
}
|
||||
|
||||
void I2sMp3Task2(void *arg){
|
||||
while (1) {
|
||||
if (audio_i2s.decoder && audio_i2s.decoder->isRunning()) {
|
||||
|
@ -534,37 +518,13 @@ void I2sStopPlaying() {
|
|||
delete audio_i2s.decoder;
|
||||
audio_i2s.decoder = NULL;
|
||||
}
|
||||
#ifdef USE_I2S_WEBRADIO
|
||||
I2sWebRadioStopPlaying();
|
||||
#endif
|
||||
|
||||
if (audio_i2s.buff) {
|
||||
audio_i2s.buff->close();
|
||||
delete audio_i2s.buff;
|
||||
audio_i2s.buff = NULL;
|
||||
}
|
||||
|
||||
if (audio_i2s.ifile) {
|
||||
audio_i2s.ifile->close();
|
||||
delete audio_i2s.ifile;
|
||||
audio_i2s.ifile = NULL;
|
||||
}
|
||||
I2SAudioPower(false);
|
||||
}
|
||||
|
||||
#ifdef USE_WEBSERVER
|
||||
const char HTTP_WEBRADIO[] PROGMEM =
|
||||
"{s}" "I2S_WR-Title" "{m}%s{e}";
|
||||
|
||||
void I2sWrShow(bool json) {
|
||||
if (audio_i2s.decoder) {
|
||||
if (json) {
|
||||
ResponseAppend_P(PSTR(",\"WebRadio\":{\"Title\":\"%s\"}"), audio_i2s.wr_title);
|
||||
} else {
|
||||
WSContentSend_PD(HTTP_WEBRADIO,audio_i2s.wr_title);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // USE_WEBSERVER
|
||||
|
||||
|
||||
// Play_mp3 - Play a MP3 file from filesystem
|
||||
//
|
||||
// Returns I2S_error_t
|
||||
|
@ -631,20 +591,6 @@ void CmndI2SStop(void) {
|
|||
ResponseCmndDone();
|
||||
}
|
||||
|
||||
void CmndI2SWebRadio(void) {
|
||||
if (!audio_i2s.out) return;
|
||||
|
||||
if (audio_i2s.decoder) {
|
||||
I2sStopPlaying();
|
||||
}
|
||||
if (XdrvMailbox.data_len > 0) {
|
||||
Webradio(XdrvMailbox.data);
|
||||
ResponseCmndChar(XdrvMailbox.data);
|
||||
} else {
|
||||
ResponseCmndChar_P(PSTR("Stopped"));
|
||||
}
|
||||
}
|
||||
|
||||
void CmndI2SPlay(void) {
|
||||
if (XdrvMailbox.data_len > 0) {
|
||||
int32_t err = I2SPlayMp3(XdrvMailbox.data);
|
||||
|
@ -785,4 +731,4 @@ bool Xdrv42(uint32_t function) {
|
|||
}
|
||||
|
||||
#endif // USE_I2S_AUDIO
|
||||
#endif //ESP_IDF_VERSION_MAJOR >= 5
|
||||
#endif // defined(ESP32) && ESP_IDF_VERSION_MAJOR >= 5
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
xdrv_42_i2s_audio.ino - Audio dac support for Tasmota
|
||||
|
||||
Copyright (C) 2021 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/>.
|
||||
*/
|
||||
|
||||
#if defined(ESP32) && ESP_IDF_VERSION_MAJOR >= 5
|
||||
#if defined(USE_I2S_AUDIO) && defined(USE_I2S_WEBRADIO)
|
||||
|
||||
struct AUDIO_I2S_WEBRADIO_t {
|
||||
// Webradio
|
||||
AudioFileSourceICYStream *ifile = NULL;
|
||||
AudioFileSourceBuffer *buff = NULL;
|
||||
char wr_title[64];
|
||||
void *preallocateBuffer = NULL;
|
||||
void *preallocateCodec = NULL;
|
||||
uint32_t retryms = 0;
|
||||
} Audio_webradio;
|
||||
|
||||
void I2sMDCallback(void *cbData, const char *type, bool isUnicode, const char *str) {
|
||||
const char *ptr = reinterpret_cast<const char *>(cbData);
|
||||
(void) isUnicode; // Punt this ball for now
|
||||
(void) ptr;
|
||||
if (strstr_P(type, PSTR("Title"))) {
|
||||
strncpy(Audio_webradio.wr_title, str, sizeof(Audio_webradio.wr_title));
|
||||
Audio_webradio.wr_title[sizeof(Audio_webradio.wr_title)-1] = 0;
|
||||
//AddLog(LOG_LEVEL_INFO,PSTR("WR-Title: %s"),wr_title);
|
||||
} else {
|
||||
// Who knows what to do? Not me!
|
||||
}
|
||||
}
|
||||
|
||||
void Webradio(const char *url) {
|
||||
// allocate buffers if not already done
|
||||
if (Audio_webradio.preallocateBuffer == NULL) {
|
||||
Audio_webradio.preallocateBuffer = special_malloc(preallocateBufferSize);
|
||||
}
|
||||
if (Audio_webradio.preallocateCodec == NULL) {
|
||||
Audio_webradio.preallocateCodec = special_malloc(preallocateCodecSize);
|
||||
}
|
||||
// check if we have buffers
|
||||
if (Audio_webradio.preallocateBuffer == NULL || Audio_webradio.preallocateCodec == NULL) {
|
||||
AddLog(LOG_LEVEL_INFO, "I2S: cannot allocate buffers");
|
||||
if (Audio_webradio.preallocateBuffer != NULL) {
|
||||
free(Audio_webradio.preallocateBuffer);
|
||||
Audio_webradio.preallocateBuffer = NULL;
|
||||
}
|
||||
if (Audio_webradio.preallocateCodec != NULL) {
|
||||
free(Audio_webradio.preallocateCodec);
|
||||
Audio_webradio.preallocateCodec = NULL;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (audio_i2s.decoder || audio_i2s.mp3) return;
|
||||
if (!audio_i2s.out) return;
|
||||
I2SAudioPower(true);
|
||||
Audio_webradio.ifile = new AudioFileSourceICYStream(url);
|
||||
Audio_webradio.ifile->RegisterMetadataCB(I2sMDCallback, NULL);
|
||||
Audio_webradio.buff = new AudioFileSourceBuffer(Audio_webradio.ifile, Audio_webradio.preallocateBuffer, preallocateBufferSize);
|
||||
Audio_webradio.buff->RegisterStatusCB(I2sStatusCallback, NULL);
|
||||
audio_i2s.decoder = new AudioGeneratorMP3(Audio_webradio.preallocateCodec, preallocateCodecSize);
|
||||
audio_i2s.decoder->RegisterStatusCB(I2sStatusCallback, NULL);
|
||||
audio_i2s.decoder->begin(Audio_webradio.buff, audio_i2s.out);
|
||||
if (!audio_i2s.decoder->isRunning()) {
|
||||
// Serial.printf_P(PSTR("Can't connect to URL"));
|
||||
I2sStopPlaying();
|
||||
// strcpy_P(status, PSTR("Unable to connect to URL"));
|
||||
Audio_webradio.retryms = millis() + 2000;
|
||||
}
|
||||
|
||||
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: will launch webradio task"));
|
||||
xTaskCreatePinnedToCore(I2sMp3Task2, "MP3-2", 8192, NULL, 3, &audio_i2s.mp3_task_handle, 1);
|
||||
}
|
||||
|
||||
#ifdef USE_WEBSERVER
|
||||
const char HTTP_WEBRADIO[] PROGMEM =
|
||||
"{s}" "I2S_WR-Title" "{m}%s{e}";
|
||||
|
||||
void I2sWrShow(bool json) {
|
||||
if (audio_i2s.decoder) {
|
||||
if (json) {
|
||||
ResponseAppend_P(PSTR(",\"WebRadio\":{\"Title\":\"%s\"}"), Audio_webradio.wr_title);
|
||||
} else {
|
||||
WSContentSend_PD(HTTP_WEBRADIO,Audio_webradio.wr_title);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // USE_WEBSERVER
|
||||
|
||||
void CmndI2SWebRadio(void) {
|
||||
if (!audio_i2s.out) return;
|
||||
|
||||
if (audio_i2s.decoder) {
|
||||
I2sStopPlaying();
|
||||
}
|
||||
if (XdrvMailbox.data_len > 0) {
|
||||
Webradio(XdrvMailbox.data);
|
||||
ResponseCmndChar(XdrvMailbox.data);
|
||||
} else {
|
||||
ResponseCmndChar_P(PSTR("Stopped"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void I2sWebRadioStopPlaying() {
|
||||
if (Audio_webradio.buff) {
|
||||
Audio_webradio.buff->close();
|
||||
delete Audio_webradio.buff;
|
||||
Audio_webradio.buff = NULL;
|
||||
}
|
||||
if (Audio_webradio.ifile) {
|
||||
Audio_webradio.ifile->close();
|
||||
delete Audio_webradio.ifile;
|
||||
Audio_webradio.ifile = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif // defined(USE_I2S_AUDIO) && defined(USE_I2S_WEBRADIO)
|
||||
#endif // defined(ESP32) && ESP_IDF_VERSION_MAJOR >= 5
|
Loading…
Reference in New Issue