mirror of
https://github.com/arendst/Tasmota.git
synced 2025-07-22 18:26:30 +00:00
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# respond to audio events
|
||||||
|
def audio(cmd, idx, payload, raw)
|
||||||
|
if cmd == "power"
|
||||||
|
self.set_speaker_enable(idx)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return AXP192_M5Stack_Core2()
|
return AXP192_M5Stack_Core2()
|
||||||
|
@ -142,6 +142,13 @@ class AXP192_M5Stack_Tough : AXP192
|
|||||||
end
|
end
|
||||||
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
|
# Touch Panel reset pin
|
||||||
def touch_reset(state)
|
def touch_reset(state)
|
||||||
self.write_gpio(1, state)
|
self.write_gpio(1, state)
|
||||||
|
@ -38,15 +38,6 @@
|
|||||||
#include <layer3.h>
|
#include <layer3.h>
|
||||||
#include <types.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
|
* 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 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 mono : 1 = 0; // bit 3 0 = stereo, 1 = mono
|
||||||
uint32_t codec : 1 = 0; // bit 4 - S3 box only, unused for now
|
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 slot_config : 3 = 0; // bit 5-7 - slot configuration MSB = 0, PCM = 1, PHILIPS = 2
|
||||||
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 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;
|
} tx;
|
||||||
struct {
|
struct {
|
||||||
struct{
|
struct{
|
||||||
@ -103,23 +95,14 @@ struct AUDIO_I2S_t {
|
|||||||
i2s_chan_handle_t rx_handle = nullptr;
|
i2s_chan_handle_t rx_handle = nullptr;
|
||||||
|
|
||||||
AudioGeneratorMP3 *mp3 = nullptr;
|
AudioGeneratorMP3 *mp3 = nullptr;
|
||||||
AudioFileSourceFS *file;
|
AudioFileSourceFS *file = nullptr;
|
||||||
|
|
||||||
TasmotaAudioOutputI2S *out;
|
TasmotaAudioOutputI2S *out = nullptr;
|
||||||
|
|
||||||
AudioFileSourceID3 *id3;
|
AudioFileSourceID3 *id3 = nullptr;
|
||||||
AudioGeneratorMP3 *decoder = NULL;
|
AudioGeneratorMP3 *decoder = NULL;
|
||||||
void *mp3ram = 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 mp3_task_handle;
|
||||||
TaskHandle_t mic_task_handle;
|
TaskHandle_t mic_task_handle;
|
||||||
|
|
||||||
@ -149,22 +132,8 @@ struct AUDIO_I2S_t {
|
|||||||
|
|
||||||
} audio_i2s;
|
} 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 };
|
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 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
|
// Constructor takes no parameter, everything is configured from template and config file
|
||||||
TasmotaAudioOutputI2S() {
|
TasmotaAudioOutputI2S() {
|
||||||
hertz = 16000;
|
loadSettings();
|
||||||
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() {
|
~TasmotaAudioOutputI2S() {
|
||||||
if(i2sOn){
|
|
||||||
this->stop();
|
this->stop();
|
||||||
i2s_del_channel(tx_chan);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------
|
||||||
// Setters for configuration parameters
|
// 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; }
|
if ( (bits != 16) && (bits != 8) ) { return false; }
|
||||||
this->bps = bits;
|
this->bps = bits;
|
||||||
return true;
|
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 < 1) || (channels > 2)) { return false; }
|
||||||
if (channels == (int)this->channels) { return true; }
|
if (channels == (int)this->channels) { return true; }
|
||||||
this->channels = channels;
|
this->channels = channels;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SetRate(int hz) {
|
virtual bool SetRate(int hz) {
|
||||||
|
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: SetRate: %i"), hz);
|
||||||
if (hz == (int) this->hertz) { return true; }
|
if (hz == (int) this->hertz) { return true; }
|
||||||
this->hertz = hz;
|
this->hertz = hz;
|
||||||
if(i2sOn){
|
if (_i2s_on) {
|
||||||
int result = updateClockConfig();
|
int result = updateClockConfig();
|
||||||
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: SetRate: %i - %i"),hz, result);
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SetPinout() { // TODO is this method still useful?
|
virtual bool SetGain(float f) {
|
||||||
return this->startI2SChannel();
|
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 begin(void);
|
||||||
bool stop(void);
|
bool stop(void);
|
||||||
bool ConsumeSample(int16_t sample[2]);
|
bool ConsumeSample(int16_t sample[2]);
|
||||||
bool startI2SChannel(void);
|
bool startI2SChannel(void);
|
||||||
int updateClockConfig(void);
|
int updateClockConfig(void);
|
||||||
|
|
||||||
protected:
|
// The following is now the preferred function
|
||||||
enum : int { EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2 };
|
// and allows to send multiple samples at once
|
||||||
int output_mode;
|
//
|
||||||
bool i2sOn;
|
// Max 128 samples, it is clipped otherwise
|
||||||
bool mono;
|
// Returns: the number of samples actually consumed
|
||||||
bool lsbJustified;
|
// 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;
|
protected:
|
||||||
bool tx_is_enabled;
|
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
|
// 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() {
|
bool TasmotaAudioOutputI2S::begin() {
|
||||||
if (tx_is_enabled) { return true; }
|
if (_tx_enabled) { return true; }
|
||||||
if (!i2sOn) {
|
if (!_i2s_on) {
|
||||||
if (audio_i2s.Settings->sys.duplex == 0 && audio_i2s.Settings->sys.rx == 1) {
|
if (audio_i2s.Settings->sys.duplex == 0 && audio_i2s.Settings->sys.rx == 1) {
|
||||||
this->startI2SChannel();
|
this->startI2SChannel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int result = i2s_channel_enable(tx_chan);
|
int result = i2s_channel_enable(_tx_chan);
|
||||||
if (result != 0){
|
if (result != 0){
|
||||||
AddLog(LOG_LEVEL_INFO, "I2S: Could not enable i2s_channel: %i", result);
|
AddLog(LOG_LEVEL_INFO, "I2S: Could not enable i2s_channel: %i", result);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
tx_is_enabled = true;
|
_tx_enabled = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TasmotaAudioOutputI2S::stop() {
|
bool TasmotaAudioOutputI2S::stop() {
|
||||||
i2s_channel_disable(tx_chan);
|
i2s_channel_disable(_tx_chan);
|
||||||
if (audio_i2s.Settings->sys.duplex == 0 && audio_i2s.Settings->sys.rx == 1) {
|
if (audio_i2s.Settings->sys.duplex == 0 && audio_i2s.Settings->sys.rx == 1) {
|
||||||
i2s_del_channel(tx_chan);
|
i2s_del_channel(_tx_chan);
|
||||||
i2sOn = false;
|
_i2s_on = false;
|
||||||
}
|
}
|
||||||
tx_is_enabled = false;
|
_tx_enabled = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TasmotaAudioOutputI2S::ConsumeSample(int16_t sample[2]) {
|
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);
|
|
||||||
|
|
||||||
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) {
|
int32_t TasmotaAudioOutputI2S::consumeSamples(int16_t *samples, size_t count) {
|
||||||
int16_t l = Amplify(ms[LEFTCHANNEL]) + 0x8000;
|
if (!_tx_enabled) { return -1; }
|
||||||
int16_t r = Amplify(ms[RIGHTCHANNEL]) + 0x8000;
|
if (count == 0) { return 0; }
|
||||||
s32 = (r << 16) | (l & 0xffff);
|
if (count > 128) { count = 128; }
|
||||||
|
|
||||||
|
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 = left + right;
|
||||||
|
left = right = (ttl>>1) & 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 {
|
} 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;
|
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;
|
return i2s_bytes_written;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize I2S channel
|
||||||
|
// return `true` if successful, `false` otherwise
|
||||||
bool TasmotaAudioOutputI2S::startI2SChannel(void) {
|
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);
|
i2s_chan_config_t tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
|
||||||
|
|
||||||
if (audio_i2s.Settings->sys.duplex == 1) {
|
if (audio_i2s.Settings->sys.duplex == 1) {
|
||||||
_DIN = (gpio_num_t)Pin(GPIO_I2S_DIN);
|
_DIN = (gpio_num_t)_gpio_din;
|
||||||
i2s_new_channel(&tx_chan_cfg, &tx_chan, &audio_i2s.rx_handle);
|
i2s_new_channel(&tx_chan_cfg, &_tx_chan, &audio_i2s.rx_handle);
|
||||||
} else{
|
} 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 = {
|
i2s_std_config_t tx_std_cfg = {
|
||||||
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(hertz),
|
.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),
|
.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG((i2s_data_bit_width_t)bps, (i2s_slot_mode_t)channels),
|
||||||
.gpio_cfg = {
|
.gpio_cfg = {
|
||||||
.mclk = (gpio_num_t)Pin(GPIO_I2S_MCLK),
|
.mclk = _gpio_mclk,
|
||||||
.bclk = (gpio_num_t)Pin(GPIO_I2S_BCLK),
|
.bclk = _gpio_bclk,
|
||||||
.ws = (gpio_num_t)Pin(GPIO_I2S_WS),
|
.ws = _gpio_ws,
|
||||||
.dout = (gpio_num_t)Pin(GPIO_I2S_DOUT),
|
.dout = _gpio_dout,
|
||||||
.din = _DIN,
|
.din = _DIN,
|
||||||
.invert_flags = {
|
.invert_flags = {
|
||||||
.mclk_inv = false,
|
.mclk_inv = _gpio_mclk_inv,
|
||||||
.bclk_inv = false,
|
.bclk_inv = _gpio_bclk_inv,
|
||||||
.ws_inv = false,
|
.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) {
|
if (audio_i2s.Settings->sys.duplex == 1) {
|
||||||
i2s_channel_init_std_mode(audio_i2s.rx_handle, &tx_std_cfg);
|
i2s_channel_init_std_mode(audio_i2s.rx_handle, &tx_std_cfg);
|
||||||
AddLog(LOG_LEVEL_DEBUG, "I2S: RX channel added in full duplex mode");
|
AddLog(LOG_LEVEL_DEBUG, "I2S: RX channel added in full duplex mode");
|
||||||
}
|
}
|
||||||
return i2sOn;
|
return _i2s_on;
|
||||||
}
|
}
|
||||||
|
|
||||||
int TasmotaAudioOutputI2S::updateClockConfig(void) {
|
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);
|
i2s_std_clk_config_t clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(hertz);
|
||||||
#ifdef SOC_I2S_SUPPORTS_APLL
|
#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;
|
clk_cfg.clk_src = I2S_CLK_SRC_APLL;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
int result = i2s_channel_reconfig_std_clock(tx_chan, &clk_cfg );
|
int result = i2s_channel_reconfig_std_clock(_tx_chan, &clk_cfg );
|
||||||
if(tx_is_enabled) i2s_channel_enable(tx_chan);
|
if (_tx_enabled) { i2s_channel_enable(_tx_chan); }
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, "I2S: Updating clock config, result=%i", result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // USE_I2S_AUDIO
|
#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
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
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))
|
#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
|
* 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 // 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/>.
|
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)
|
#if defined(USE_I2S_AUDIO)
|
||||||
|
|
||||||
#define XDRV_42 42
|
#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
|
* Commands definitions
|
||||||
\*********************************************************************************************/
|
\*********************************************************************************************/
|
||||||
|
|
||||||
const char kI2SAudio_Commands[] PROGMEM = "I2S|"
|
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
|
#ifdef USE_I2S_SAY
|
||||||
"|Say"
|
"|Say"
|
||||||
#endif // USE_I2S_SAY
|
#endif // USE_I2S_SAY
|
||||||
@ -46,7 +75,10 @@ const char kI2SAudio_Commands[] PROGMEM = "I2S|"
|
|||||||
;
|
;
|
||||||
|
|
||||||
void (* const I2SAudio_Command[])(void) PROGMEM = {
|
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
|
#ifdef USE_I2S_SAY
|
||||||
&CmndI2SSay,
|
&CmndI2SSay,
|
||||||
#endif // USE_I2S_SAY
|
#endif // USE_I2S_SAY
|
||||||
@ -378,7 +410,6 @@ void I2sCheckCfg(void){
|
|||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
audio_i2s.Settings->sys.tx = 0;
|
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));
|
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){
|
if(audio_i2s.Settings->sys.tx == 1){
|
||||||
audio_i2s.out = new TasmotaAudioOutputI2S;
|
audio_i2s.out = new TasmotaAudioOutputI2S;
|
||||||
int err = audio_i2s.out->SetPinout() ? 0 : 1;
|
int err = audio_i2s.out->startI2SChannel() ? 0 : 1;
|
||||||
result += err;
|
result += err;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -431,17 +462,6 @@ void I2sInit(void) {
|
|||||||
audio_i2s.Settings->rx.mp3_encoder = 0; // no PS-RAM -> no MP3 encoding
|
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) {
|
void I2sStatusCallback(void *cbData, int code, const char *string) {
|
||||||
const char *ptr = reinterpret_cast<const char *>(cbData);
|
const char *ptr = reinterpret_cast<const char *>(cbData);
|
||||||
(void) code;
|
(void) code;
|
||||||
@ -487,29 +494,6 @@ void I2sStatusCallback(void *cbData, int code, const char *string) {
|
|||||||
//status[sizeof(status)-1] = 0;
|
//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){
|
void I2sMp3Task2(void *arg){
|
||||||
while (1) {
|
while (1) {
|
||||||
if (audio_i2s.decoder && audio_i2s.decoder->isRunning()) {
|
if (audio_i2s.decoder && audio_i2s.decoder->isRunning()) {
|
||||||
@ -534,37 +518,13 @@ void I2sStopPlaying() {
|
|||||||
delete audio_i2s.decoder;
|
delete audio_i2s.decoder;
|
||||||
audio_i2s.decoder = NULL;
|
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);
|
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
|
// Play_mp3 - Play a MP3 file from filesystem
|
||||||
//
|
//
|
||||||
// Returns I2S_error_t
|
// Returns I2S_error_t
|
||||||
@ -631,20 +591,6 @@ void CmndI2SStop(void) {
|
|||||||
ResponseCmndDone();
|
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) {
|
void CmndI2SPlay(void) {
|
||||||
if (XdrvMailbox.data_len > 0) {
|
if (XdrvMailbox.data_len > 0) {
|
||||||
int32_t err = I2SPlayMp3(XdrvMailbox.data);
|
int32_t err = I2SPlayMp3(XdrvMailbox.data);
|
||||||
@ -785,4 +731,4 @@ bool Xdrv42(uint32_t function) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endif // USE_I2S_AUDIO
|
#endif // USE_I2S_AUDIO
|
||||||
#endif //ESP_IDF_VERSION_MAJOR >= 5
|
#endif // defined(ESP32) && ESP_IDF_VERSION_MAJOR >= 5
|
||||||
|
134
tasmota/tasmota_xdrv_driver/xdrv_42_7_i2s_webradio_idf51.ino
Normal file
134
tasmota/tasmota_xdrv_driver/xdrv_42_7_i2s_webradio_idf51.ino
Normal file
@ -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…
x
Reference in New Issue
Block a user