mirror of https://github.com/arendst/Tasmota.git
Audio prepare for Arduino3 (#19637)
This commit is contained in:
parent
0be4613cf9
commit
795a194d65
|
@ -97,6 +97,35 @@ BE_FUNC_CTYPE_DECLARE(be_audio_input_i2s_init, "+.p", "");
|
||||||
extern void* be_audio_input_i2s_deinit(void* instance);
|
extern void* be_audio_input_i2s_deinit(void* instance);
|
||||||
BE_FUNC_CTYPE_DECLARE(be_audio_input_i2s_deinit, "", ".");
|
BE_FUNC_CTYPE_DECLARE(be_audio_input_i2s_deinit, "", ".");
|
||||||
|
|
||||||
|
// AudioInputI2S.begin() -> bool
|
||||||
|
extern int be_audio_input_i2s_begin(bvm *vm, void* in);
|
||||||
|
BE_FUNC_CTYPE_DECLARE(be_audio_input_i2s_begin, "b", "@.");
|
||||||
|
|
||||||
|
// AudioInputI2S.stop() -> bool
|
||||||
|
extern int be_audio_input_i2s_stop(void* in);
|
||||||
|
BE_FUNC_CTYPE_DECLARE(be_audio_input_i2s_stop, "b", ".");
|
||||||
|
|
||||||
|
extern int be_audio_input_i2s_read_bytes(bvm *vm);
|
||||||
|
|
||||||
|
// AudioInputI2S.set_gain(gain:real) -> bool
|
||||||
|
extern int be_audio_input_set_gain(void* in, float gain);
|
||||||
|
BE_FUNC_CTYPE_DECLARE(be_audio_input_set_gain, "b", ".f");
|
||||||
|
|
||||||
|
// AudioInputI2S.get_rate() -> int
|
||||||
|
extern int be_audio_input_i2s_get_rate(void* in);
|
||||||
|
BE_FUNC_CTYPE_DECLARE(be_audio_input_i2s_get_rate, "i", ".");
|
||||||
|
|
||||||
|
// AudioInputI2S.get_bits_per_sample() -> int
|
||||||
|
extern int be_audio_input_i2s_get_bits_per_sample(void* in);
|
||||||
|
BE_FUNC_CTYPE_DECLARE(be_audio_input_i2s_get_bits_per_sample, "i", ".");
|
||||||
|
|
||||||
|
// AudioInputI2S.get_channels() -> int
|
||||||
|
extern int be_audio_input_i2s_get_channels(void* in);
|
||||||
|
BE_FUNC_CTYPE_DECLARE(be_audio_input_i2s_get_channels, "i", ".");
|
||||||
|
|
||||||
|
// AudioInputI2S.get_gain() -> real
|
||||||
|
extern float be_audio_input_i2s_get_gain(void* in);
|
||||||
|
BE_FUNC_CTYPE_DECLARE(be_audio_input_i2s_get_gain, "f", ".");
|
||||||
|
|
||||||
#include "be_fixed_be_class_AudioOutputI2S.h"
|
#include "be_fixed_be_class_AudioOutputI2S.h"
|
||||||
#include "be_fixed_be_class_AudioGenerator.h"
|
#include "be_fixed_be_class_AudioGenerator.h"
|
||||||
|
@ -162,6 +191,17 @@ class be_class_AudioInputI2S (scope: global, name: AudioInputI2S, strings: weak)
|
||||||
.p, var
|
.p, var
|
||||||
init, ctype_func(be_audio_input_i2s_init)
|
init, ctype_func(be_audio_input_i2s_init)
|
||||||
deinit, ctype_func(be_audio_input_i2s_deinit)
|
deinit, ctype_func(be_audio_input_i2s_deinit)
|
||||||
|
|
||||||
|
begin, ctype_func(be_audio_input_i2s_begin)
|
||||||
|
stop, ctype_func(be_audio_input_i2s_stop)
|
||||||
|
read_bytes, func(be_audio_input_i2s_read_bytes)
|
||||||
|
|
||||||
|
get_rate, ctype_func(be_audio_input_i2s_get_rate)
|
||||||
|
get_bits_per_sample, ctype_func(be_audio_input_i2s_get_bits_per_sample)
|
||||||
|
get_channels, ctype_func(be_audio_input_i2s_get_channels)
|
||||||
|
get_gain, ctype_func(be_audio_input_i2s_get_gain)
|
||||||
|
|
||||||
|
set_gain, ctype_func(be_audio_input_set_gain)
|
||||||
}
|
}
|
||||||
|
|
||||||
@const_object_info_end */
|
@const_object_info_end */
|
||||||
|
|
|
@ -0,0 +1,168 @@
|
||||||
|
/*
|
||||||
|
xdrv_42_0_i2s_0_config_idf51.ino - Simplified Audio library, general configuration
|
||||||
|
|
||||||
|
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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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 "soc/soc_caps.h"
|
||||||
|
|
||||||
|
#include "AudioFileSourcePROGMEM.h"
|
||||||
|
#include "AudioFileSourceID3.h"
|
||||||
|
#include "AudioGeneratorMP3.h"
|
||||||
|
|
||||||
|
#include <ESP8266SAM.h>
|
||||||
|
#include "AudioFileSourceFS.h"
|
||||||
|
#include "AudioGeneratorTalkie.h"
|
||||||
|
#include "AudioFileSourceICYStream.h"
|
||||||
|
#include "AudioFileSourceBuffer.h"
|
||||||
|
#include "AudioGeneratorAAC.h"
|
||||||
|
|
||||||
|
#include <layer3.h>
|
||||||
|
#include <types.h>
|
||||||
|
|
||||||
|
/*********************************************************************************************\
|
||||||
|
* Driver Settings in memory
|
||||||
|
\*********************************************************************************************/
|
||||||
|
|
||||||
|
// I2S communication mode
|
||||||
|
enum : int8_t {
|
||||||
|
I2S_MODE_STD = 0, // I2S mode standard
|
||||||
|
I2S_MODE_PDM = 1, // I2S mode PDM
|
||||||
|
I2S_MODE_TDM = 2, // I2S mode TDM
|
||||||
|
I2S_MODE_DAC = 3, // Using internal DAC - only available on ESP32
|
||||||
|
};
|
||||||
|
|
||||||
|
// I2S slot mask (left, right, both)
|
||||||
|
enum : int8_t {
|
||||||
|
I2S_SLOT_NOCHANGE = 0, // don't change default
|
||||||
|
I2S_SLOT_LEFT = 1, // left
|
||||||
|
I2S_SLOT_RIGHT = 2, // right
|
||||||
|
I2S_SLOT_BOTH = 3, // both
|
||||||
|
};
|
||||||
|
|
||||||
|
// I2S slot configuration
|
||||||
|
enum : int8_t {
|
||||||
|
I2S_SLOT_MSB = 0, // MSB
|
||||||
|
I2S_SLOT_PCM = 1, // PCM
|
||||||
|
I2S_SLOT_PHILIPS = 2, // Philips
|
||||||
|
};
|
||||||
|
|
||||||
|
#define I2S_SLOTS 2
|
||||||
|
|
||||||
|
typedef struct{
|
||||||
|
struct{
|
||||||
|
uint8_t version = 0; // B00
|
||||||
|
|
||||||
|
// runtime options, will be saved but ignored on setting read
|
||||||
|
bool duplex = 0; // B01 - depends on GPIO setting and SOC caps, DIN and DOUT on same port in GPIO means -> try to use duplex if possible
|
||||||
|
bool tx = 0; // B02 - depends on GPIO setting
|
||||||
|
bool rx = 0; // B03 - depends on GPIO setting
|
||||||
|
bool exclusive = 0; // B04 - depends on GPIO setting, if WS is shared between 2 ports, drivers needs to be reinstalled before being used (Yuck... but we don't have a choice)
|
||||||
|
|
||||||
|
bool mclk_inv[I2S_SLOTS] = {0}; // B05-06 - invert mclk
|
||||||
|
bool bclk_inv[I2S_SLOTS] = {0}; // B07-08 - invert bclk
|
||||||
|
bool ws_inv[I2S_SLOTS] = {0}; // B09-0A - invert ws
|
||||||
|
uint8_t spare[5]; // B0B-0F
|
||||||
|
} sys;
|
||||||
|
struct {
|
||||||
|
uint32_t sample_rate = 16000; // B00-03
|
||||||
|
uint8_t gain = 10; // B04 - was `volume`
|
||||||
|
uint8_t mode = I2S_MODE_STD; // B05 - I2S mode standard, PDM, TDM, DAC
|
||||||
|
uint8_t slot_mask = I2S_SLOT_NOCHANGE;// B06 - slot mask
|
||||||
|
uint8_t slot_config = I2S_SLOT_MSB;// B07 - slot configuration MSB = 0, PCM = 1, PHILIPS = 2
|
||||||
|
uint8_t channels = 2; // B08 - mono/stereo - 1 is added for both
|
||||||
|
bool apll = 1; // B09 - will be ignored on unsupported SOC's
|
||||||
|
// device specific
|
||||||
|
uint8_t mp3_preallocate = 0; // B0A - preallocate MP3 buffer for mp3 playing
|
||||||
|
uint8_t codec = 0; // B0B - S3 box only, unused for now
|
||||||
|
uint8_t spare[4]; // B0C-0F
|
||||||
|
} tx;
|
||||||
|
struct {
|
||||||
|
uint32_t sample_rate = 16000; // B00-03
|
||||||
|
uint8_t gain = 30; // B04
|
||||||
|
uint8_t mode = I2S_MODE_PDM; // B05 - I2S mode standard, PDM, TDM, DAC
|
||||||
|
uint8_t slot_mask = I2S_SLOT_NOCHANGE;// B06 - slot mask
|
||||||
|
uint8_t slot_config = I2S_SLOT_MSB;// B07 - slot configuration MSB = 0, PCM = 1, PHILIPS = 2
|
||||||
|
uint8_t channels = 1; // B08 - mono/stereo - 1 is added for both
|
||||||
|
bool apll = 1; // B09 - will be ignored on unsupported SOC's
|
||||||
|
// device specific
|
||||||
|
uint8_t codec = 0; // B0A - S3 box only, unused for now
|
||||||
|
uint8_t mp3_preallocate = 0; // B0B - will be ignored without PS-RAM
|
||||||
|
uint8_t spare[4]; // B0C-0F
|
||||||
|
} 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 TasmotaI2S;
|
||||||
|
|
||||||
|
struct AUDIO_I2S_t {
|
||||||
|
tI2SSettings *Settings;
|
||||||
|
|
||||||
|
i2s_chan_handle_t rx_handle = nullptr;
|
||||||
|
|
||||||
|
AudioGeneratorMP3 *mp3 = nullptr;
|
||||||
|
AudioFileSourceFS *file = nullptr;
|
||||||
|
|
||||||
|
TasmotaI2S *out = nullptr; // instance used for I2S output, or `nullptr` if none
|
||||||
|
TasmotaI2S *in = nullptr; // instance used for I2S input, or `nullptr` if none (it can be the same as `out` in case of full duplex)
|
||||||
|
|
||||||
|
AudioFileSourceID3 *id3 = nullptr;
|
||||||
|
AudioGeneratorMP3 *decoder = NULL;
|
||||||
|
void *mp3ram = NULL;
|
||||||
|
|
||||||
|
TaskHandle_t mp3_task_handle;
|
||||||
|
TaskHandle_t mic_task_handle;
|
||||||
|
|
||||||
|
char mic_path[32];
|
||||||
|
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;
|
||||||
|
|
||||||
|
#endif // USE_I2S_AUDIO
|
||||||
|
#endif // defined(ESP32) && ESP_IDF_VERSION_MAJOR >= 5
|
|
@ -0,0 +1,603 @@
|
||||||
|
/*
|
||||||
|
xdrv_42_0_i2s_0_lib_idf51.ino - Simplified Audio library, core class
|
||||||
|
|
||||||
|
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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if defined(ESP32) && ESP_IDF_VERSION_MAJOR >= 5
|
||||||
|
#ifdef USE_I2S_AUDIO
|
||||||
|
|
||||||
|
/*********************************************************************************************\
|
||||||
|
* Driver Settings in memory
|
||||||
|
\*********************************************************************************************/
|
||||||
|
|
||||||
|
/*********************************************************************************************\
|
||||||
|
* Reminder of esp_err codes
|
||||||
|
*
|
||||||
|
* #define ESP_OK 0 - esp_err_t value indicating success (no error)
|
||||||
|
* #define ESP_FAIL -1 - Generic esp_err_t code indicating failure
|
||||||
|
*
|
||||||
|
* #define ESP_ERR_NO_MEM 0x101 257 - Out of memory
|
||||||
|
* #define ESP_ERR_INVALID_ARG 0x102 258 - Invalid argument
|
||||||
|
* #define ESP_ERR_INVALID_STATE 0x103 259 - Invalid state
|
||||||
|
* #define ESP_ERR_INVALID_SIZE 0x104 260 - Invalid size
|
||||||
|
* #define ESP_ERR_NOT_FOUND 0x105 261 - Requested resource not found
|
||||||
|
* #define ESP_ERR_NOT_SUPPORTED 0x106 262 - Operation or feature not supported
|
||||||
|
* #define ESP_ERR_TIMEOUT 0x107 263 - Operation timed out
|
||||||
|
* #define ESP_ERR_INVALID_RESPONSE 0x108 264 - Received response was invalid
|
||||||
|
* #define ESP_ERR_INVALID_CRC 0x109 265 - CRC or checksum was invalid
|
||||||
|
* #define ESP_ERR_INVALID_VERSION 0x10A 266 - Version was invalid
|
||||||
|
* #define ESP_ERR_INVALID_MAC 0x10B 267 - MAC address was invalid
|
||||||
|
* #define ESP_ERR_NOT_FINISHED 0x10C 268 - Operation has not fully completed
|
||||||
|
\*********************************************************************************************/
|
||||||
|
|
||||||
|
/*********************************************************************************************\
|
||||||
|
* This is the central class to acccess I2S in (rx) or out (tx)
|
||||||
|
*
|
||||||
|
* It inherits from AudioOutput so it can be used as output instance for ESP8266Audio library
|
||||||
|
*
|
||||||
|
* It also supports microphone input
|
||||||
|
*
|
||||||
|
\*********************************************************************************************/
|
||||||
|
|
||||||
|
class TasmotaI2S : public AudioOutput
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
// Constructor takes no parameter, everything is configured from template and config file
|
||||||
|
TasmotaI2S() {
|
||||||
|
// set some defaults
|
||||||
|
hertz = 16000;
|
||||||
|
bps = I2S_DATA_BIT_WIDTH_16BIT;
|
||||||
|
channels = 2;
|
||||||
|
gainF2P6 = 32; // equivalent of 0.5
|
||||||
|
}
|
||||||
|
|
||||||
|
~TasmotaI2S() {
|
||||||
|
this->stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Settings
|
||||||
|
void setPinout(int32_t bclk, int32_t ws, int32_t dout, int32_t mclk, int32_t din,
|
||||||
|
bool mclk_inv = false, bool bclk_inv = false, bool ws_inv = false, bool apll = false);
|
||||||
|
|
||||||
|
void setSlotConfig(i2s_port_t i2s_port, uint8_t tx_slot_config, uint8_t rx_slot_config,
|
||||||
|
uint8_t tx_slot_mask, uint8_t rx_slot_mask) {
|
||||||
|
_i2s_port = i2s_port;
|
||||||
|
_tx_slot_config = tx_slot_config;
|
||||||
|
_rx_slot_config = rx_slot_config;
|
||||||
|
}
|
||||||
|
void setRxFreq(uint16_t freq) { _rx_freq = freq; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------
|
||||||
|
// Setters for configuration parameters
|
||||||
|
//
|
||||||
|
// TODO: not sure we still need them since all this should be set at initialiation
|
||||||
|
// ------------------------------------------------------------------------------------------
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool SetRate(int hz) {
|
||||||
|
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: SetRate: %i was %i on=%i"), hz, this->hertz, _i2s_on);
|
||||||
|
if (hz == (int) this->hertz) { return true; }
|
||||||
|
this->hertz = hz;
|
||||||
|
if (_i2s_on) {
|
||||||
|
int result = updateClockConfig();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool SetGain(float f) {
|
||||||
|
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: SetGain: %_f"), &f);
|
||||||
|
return AudioOutput::SetGain(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------
|
||||||
|
// Getters
|
||||||
|
inline bool isDuplex(void) const { return _tx_configured && _rx_configured; }
|
||||||
|
|
||||||
|
inline bool getExclusive(void) const {return _exclusive; }
|
||||||
|
inline uint8_t getTxMode(void) const { return _tx_mode; }
|
||||||
|
inline uint8_t getTxChannels(void) const { return channels; }
|
||||||
|
inline bool getTxRunning(void) const { return _tx_running; }
|
||||||
|
inline i2s_chan_handle_t getTxHandle(void) const { return _tx_handle; }
|
||||||
|
|
||||||
|
inline uint8_t getRxMode(void) const { return _rx_mode; }
|
||||||
|
inline uint8_t getRxBitsPerSample(void) const { return 16; } // TODO - hardcoded to 16 bits for recording
|
||||||
|
inline uint16_t getRxRate(void) const { return _rx_freq; }
|
||||||
|
inline uint8_t getRxChannels(void) const { return _rx_channels; }
|
||||||
|
inline float getRxGain(void) const { return 1.0f; } // TODO - hardcoded to 1.0 for recording
|
||||||
|
inline bool getRxRunning(void) const { return _rx_running; }
|
||||||
|
inline i2s_chan_handle_t getRxHandle(void) const { return _rx_handle; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------
|
||||||
|
// Setters
|
||||||
|
inline void setExclusive(bool exclusive) { _exclusive = exclusive; }
|
||||||
|
inline void setTxMode(uint8_t mode) { _tx_mode = mode; }
|
||||||
|
inline void setTxChannels(uint8_t channels) { SetChannels(channels); }
|
||||||
|
inline void setTxRunning(bool running) { _tx_running = running; }
|
||||||
|
inline void setRxMode(uint8_t mode) { _rx_mode = mode; }
|
||||||
|
inline void setRxChannels(uint8_t channels) { _rx_channels = channels; }
|
||||||
|
inline void setRxRunning(bool running) { _rx_running = running; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------
|
||||||
|
// 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(bool tx, bool rx);
|
||||||
|
int updateClockConfig(void);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------
|
||||||
|
// Microphone related methods
|
||||||
|
uint32_t I2sMicInit(void);
|
||||||
|
void I2sMicDeinit(void);
|
||||||
|
|
||||||
|
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
|
||||||
|
bool _exclusive = false; // in exclusive mode, stopping this instance needs to uninstall driver, and reinstall for next use
|
||||||
|
i2s_port_t _i2s_port = I2S_NUM_AUTO; // I2S port, I2S_NUM_0/I2S_NUM_1/I2S_NUM_AUTO
|
||||||
|
|
||||||
|
// local copy of useful settings for audio
|
||||||
|
// TX
|
||||||
|
bool _tx_configured = false; // true = configured, false = not configured
|
||||||
|
uint8_t _tx_mode = I2S_MODE_STD; // I2S_MODE_STD / I2S_MODE_PDM / I2S_MODE_TDM / I2S_MODE_DAC
|
||||||
|
uint8_t _tx_slot_mask = I2S_SLOT_NOCHANGE;
|
||||||
|
bool _tx_running = false; // true = enabled, false = disabled
|
||||||
|
// uint8_t _tx_channels = 2; // number of channels, 1 = mono, 2 = stereo -- `channels`
|
||||||
|
i2s_chan_handle_t _tx_handle = nullptr; // I2S channel handle, automatically computed
|
||||||
|
uint8_t _tx_slot_config = I2S_SLOT_MSB;// I2S slot configuration
|
||||||
|
|
||||||
|
// RX
|
||||||
|
bool _rx_configured = false; // true = configured, false = not configured
|
||||||
|
uint8_t _rx_mode = I2S_MODE_STD; // I2S_MODE_STD / I2S_MODE_PDM / I2S_MODE_TDM / I2S_MODE_DAC
|
||||||
|
uint8_t _rx_slot_mask = I2S_SLOT_NOCHANGE;
|
||||||
|
bool _rx_running = false; // true = enabled, false = disabled
|
||||||
|
uint8_t _rx_channels = 2; // number of channels, 1 = mono, 2 = stereo
|
||||||
|
i2s_chan_handle_t _rx_handle = nullptr; // I2S channel handle, automatically computed
|
||||||
|
uint8_t _rx_slot_config = I2S_SLOT_MSB;// I2S slot configuration
|
||||||
|
uint16_t _rx_freq = 16000; // I2S Rx sampling frequency in Hz
|
||||||
|
|
||||||
|
// 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
|
||||||
|
bool _apll = false; // use APLL instead of PLL
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------
|
||||||
|
// Methods
|
||||||
|
// ------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void TasmotaI2S::setPinout(int32_t bclk, int32_t ws, int32_t dout, int32_t mclk, int32_t din,
|
||||||
|
bool mclk_inv, bool bclk_inv, bool ws_inv, bool apll) {
|
||||||
|
_gpio_mclk = (gpio_num_t) mclk;
|
||||||
|
_gpio_bclk = (gpio_num_t) bclk;
|
||||||
|
_gpio_ws = (gpio_num_t) ws;
|
||||||
|
_gpio_dout = (gpio_num_t) dout;
|
||||||
|
_gpio_din = (gpio_num_t) din;
|
||||||
|
_gpio_mclk_inv = mclk_inv;
|
||||||
|
_gpio_bclk_inv = bclk_inv;
|
||||||
|
_gpio_ws_inv = ws_inv;
|
||||||
|
_apll = apll;
|
||||||
|
|
||||||
|
_tx_configured = (_gpio_dout != GPIO_NUM_NC);
|
||||||
|
_rx_configured = (_gpio_din != GPIO_NUM_NC);
|
||||||
|
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, "I2S: setPinout: gpios[%i,%i,%i,%i,%i] inv[%i,%i,%i] apll:%i _tx_configured:%i _rx_configured:%i",
|
||||||
|
_gpio_mclk, _gpio_bclk, _gpio_ws, _gpio_dout, _gpio_din,
|
||||||
|
_gpio_mclk_inv, _gpio_bclk_inv, _gpio_ws_inv, _apll,
|
||||||
|
_tx_configured, _rx_configured);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TasmotaI2S::begin() {
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, "I2S: begin _tx_running:%i _i2s_on:%i", _tx_running, _i2s_on);
|
||||||
|
if (_tx_running) { return true; }
|
||||||
|
// if (!_i2s_on) {
|
||||||
|
// if ((!_rx_configured || !_tx_configured) && _rx_configured) { // not duplex -- TODO ?
|
||||||
|
// this->startI2SChannel();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
int result = i2s_channel_enable(_tx_handle);
|
||||||
|
if (result != 0){
|
||||||
|
AddLog(LOG_LEVEL_INFO, "I2S: Could not enable i2s_channel: %i", result);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_tx_running = true;
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, "I2S: begin _tx_running succeeded");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TasmotaI2S::stop() {
|
||||||
|
i2s_channel_disable(_tx_handle);
|
||||||
|
if ((!_rx_configured || !_tx_configured) && _rx_configured) { // not duplex -- TODO ?
|
||||||
|
i2s_del_channel(_tx_handle);
|
||||||
|
_i2s_on = false;
|
||||||
|
AddLog(LOG_LEVEL_INFO, "I2S: stop: I2S channel disabled");
|
||||||
|
}
|
||||||
|
_tx_running = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TasmotaI2S::ConsumeSample(int16_t sample[2]) {
|
||||||
|
return consumeSamples(sample, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t TasmotaI2S::consumeSamples(int16_t *samples, size_t count) {
|
||||||
|
if (!_tx_running) { return -1; }
|
||||||
|
if (count == 0) { return 0; }
|
||||||
|
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 (channels == 1) { // if mono, average the two samples
|
||||||
|
// 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 == I2S_MODE_DAC) {
|
||||||
|
left = Amplify(left) + 0x8000;
|
||||||
|
right = Amplify(right) + 0x8000;
|
||||||
|
} else {
|
||||||
|
left = Amplify(left);
|
||||||
|
right = Amplify(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
ms[i*2 + LEFTCHANNEL] = left;
|
||||||
|
ms[i*2 + RIGHTCHANNEL] = right;
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddLog(LOG_LEVEL_DEBUG, "I2S: consumeSamples: left=%i right=%i", ms[0], ms[1]);
|
||||||
|
|
||||||
|
size_t i2s_bytes_written;
|
||||||
|
esp_err_t err = i2s_channel_write(_tx_handle, 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 TasmotaI2S::startI2SChannel(bool tx, bool rx) {
|
||||||
|
if (!tx) { _tx_configured = false; }
|
||||||
|
if (!rx) { _rx_configured = false; }
|
||||||
|
if (!_tx_configured && !_rx_configured) { return false; } // nothing configured
|
||||||
|
|
||||||
|
esp_err_t err = ESP_OK;
|
||||||
|
gpio_num_t _DIN = I2S_GPIO_UNUSED; // no input pin by default
|
||||||
|
|
||||||
|
if (_tx_configured) {
|
||||||
|
// 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_1, I2S_ROLE_MASTER);
|
||||||
|
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, "I2S: tx_chan_cfg id:%i role:%i dma_desc_num:%i dma_frame_num:%i auto_clear:%i",
|
||||||
|
tx_chan_cfg.id, tx_chan_cfg.role, tx_chan_cfg.dma_desc_num, tx_chan_cfg.dma_frame_num, tx_chan_cfg.auto_clear);
|
||||||
|
|
||||||
|
if (_tx_configured && _rx_configured) {
|
||||||
|
_DIN = (gpio_num_t)_gpio_din;
|
||||||
|
err = i2s_new_channel(&tx_chan_cfg, &_tx_handle, &audio_i2s.out->_rx_handle);
|
||||||
|
} else{
|
||||||
|
err = i2s_new_channel(&tx_chan_cfg, &_tx_handle, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, "I2S: i2s_new_channel err:%i", err);
|
||||||
|
|
||||||
|
// 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_mclk,
|
||||||
|
.bclk = _gpio_bclk,
|
||||||
|
.ws = _gpio_ws,
|
||||||
|
.dout = _gpio_dout,
|
||||||
|
.din = _DIN,
|
||||||
|
.invert_flags = {
|
||||||
|
.mclk_inv = _gpio_mclk_inv,
|
||||||
|
.bclk_inv = _gpio_bclk_inv,
|
||||||
|
.ws_inv = _gpio_ws_inv,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// change configuration if we are using PCM or PHILIPS
|
||||||
|
if (_tx_slot_config == I2S_SLOT_PCM) { // 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 (_tx_slot_config == I2S_SLOT_PHILIPS) { // PHILIPS
|
||||||
|
tx_std_cfg.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG((i2s_data_bit_width_t)bps, (i2s_slot_mode_t)channels);
|
||||||
|
}
|
||||||
|
if (_tx_slot_mask != I2S_SLOT_NOCHANGE) { tx_std_cfg.slot_cfg.slot_mask = (i2s_std_slot_mask_t)_tx_slot_mask; }
|
||||||
|
|
||||||
|
// AddLog(LOG_LEVEL_INFO, ">>>: I2S tx_chan_cfg=%*_H", sizeof(tx_chan_cfg), &tx_chan_cfg);
|
||||||
|
// AddLog(LOG_LEVEL_INFO, ">>>: I2S tx_std_cfg=%*_H", sizeof(tx_std_cfg), &tx_std_cfg);
|
||||||
|
|
||||||
|
err = i2s_channel_init_std_mode(_tx_handle, &tx_std_cfg);
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, "I2S: TX channel bits:%i channels:%i hertz:%i initialized err=0x%04X", bps, channels, hertz, err);
|
||||||
|
if (err != ERR_OK) {
|
||||||
|
_i2s_on = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_i2s_on = true;
|
||||||
|
if (_rx_configured) { // full duplex mode
|
||||||
|
err = i2s_channel_init_std_mode(audio_i2s.out->_rx_handle, &tx_std_cfg);
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, "I2S: i2s_channel_init_std_mode err:%i", err);
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, "I2S: RX channel added in full duplex mode");
|
||||||
|
}
|
||||||
|
} // if (tx)
|
||||||
|
|
||||||
|
// configure Rx Microphone
|
||||||
|
if (_rx_configured && !_tx_configured) { // if Microphone and not duplex
|
||||||
|
gpio_num_t clk_gpio;
|
||||||
|
|
||||||
|
i2s_slot_mode_t slot_mode = (_rx_channels == 1) ? I2S_SLOT_MODE_MONO : I2S_SLOT_MODE_STEREO;
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, "I2S: mic init rx_channels:%i rx_running:%i rx_handle:%p", slot_mode, _rx_running, _rx_handle);
|
||||||
|
|
||||||
|
if (_tx_configured && _rx_running) { // duplex mode, mic was already initialized
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, "I2S: mic init exit Rx already enabled");
|
||||||
|
return 0; // no need to en- or disable when in full duplex mode and already initialized
|
||||||
|
}
|
||||||
|
|
||||||
|
i2s_chan_config_t rx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
|
||||||
|
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, "I2S: rx_chan_cfg id:%i role:%i dma_desc_num:%i dma_frame_num:%i auto_clear:%i",
|
||||||
|
rx_chan_cfg.id, rx_chan_cfg.role, rx_chan_cfg.dma_desc_num, rx_chan_cfg.dma_frame_num, rx_chan_cfg.auto_clear);
|
||||||
|
|
||||||
|
err = i2s_new_channel(&rx_chan_cfg, NULL, &_rx_handle);
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, "I2S: mic init i2s_new_channel err=%i", err);
|
||||||
|
switch (_rx_mode){
|
||||||
|
case I2S_MODE_PDM:
|
||||||
|
{
|
||||||
|
i2s_pdm_rx_config_t rx_pdm_cfg = {
|
||||||
|
.clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(_rx_freq),
|
||||||
|
/* The default mono slot is the left slot (whose 'select pin' of the PDM microphone is pulled down) */
|
||||||
|
.slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, (i2s_slot_mode_t)_rx_channels),
|
||||||
|
.gpio_cfg = {
|
||||||
|
.clk = _gpio_ws,
|
||||||
|
.din = _gpio_din,
|
||||||
|
.invert_flags = {
|
||||||
|
.clk_inv = _gpio_ws_inv,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// if (_rx_slot_mask != I2S_SLOT_NOCHANGE) { rx_pdm_cfg.slot_cfg.slot_mask = (i2s_pdm_slot_mask_t)_rx_slot_mask; }
|
||||||
|
|
||||||
|
// AddLog(LOG_LEVEL_DEBUG, "I2S: rx_pdm_cfg clk_cfg sample_rate_hz:%i clk_src:%i mclk_multiple:%i dn_sample_mode:%i",
|
||||||
|
// rx_pdm_cfg.clk_cfg.sample_rate_hz, rx_pdm_cfg.clk_cfg.clk_src, rx_pdm_cfg.clk_cfg.mclk_multiple, rx_pdm_cfg.clk_cfg.dn_sample_mode);
|
||||||
|
|
||||||
|
// AddLog(LOG_LEVEL_DEBUG, "I2S: rx_pdm_cfg slot_cfg data_bit_width:%i slot_bit_width:%i slot_mode:%i slot_mask:%i",
|
||||||
|
// rx_pdm_cfg.slot_cfg.data_bit_width, rx_pdm_cfg.slot_cfg.slot_bit_width, rx_pdm_cfg.slot_cfg.slot_mode, rx_pdm_cfg.slot_cfg.slot_mask);
|
||||||
|
|
||||||
|
// AddLog(LOG_LEVEL_DEBUG, "I2S: rx_pdm_cfg gpio_cfg clk:%i din:%i clk_inv:%i",
|
||||||
|
// rx_pdm_cfg.gpio_cfg.clk, rx_pdm_cfg.gpio_cfg.din, rx_pdm_cfg.gpio_cfg.invert_flags.clk_inv);
|
||||||
|
|
||||||
|
err = i2s_channel_init_pdm_rx_mode(_rx_handle, &rx_pdm_cfg);
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: RX channel in PDM mode, CLK: %i, DIN: %i, 16 bit width, %i channel(s), err code: 0x%04X"),
|
||||||
|
_gpio_ws, _gpio_din, _rx_channels, err);
|
||||||
|
_i2s_on = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case I2S_MODE_STD:
|
||||||
|
{
|
||||||
|
i2s_std_config_t rx_std_cfg = {
|
||||||
|
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(_rx_freq),
|
||||||
|
.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, slot_mode),
|
||||||
|
.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 = I2S_GPIO_UNUSED,
|
||||||
|
.din = (gpio_num_t)Pin(GPIO_I2S_DIN),
|
||||||
|
.invert_flags = {
|
||||||
|
.mclk_inv = false,
|
||||||
|
.bclk_inv = false,
|
||||||
|
.ws_inv = false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (_rx_slot_mask != I2S_SLOT_NOCHANGE) { rx_std_cfg.slot_cfg.slot_mask = (i2s_std_slot_mask_t)_rx_slot_mask; }
|
||||||
|
|
||||||
|
err = i2s_channel_init_std_mode(audio_i2s.rx_handle, &rx_std_cfg);
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, "I2S: RX i2s_channel_init_std_mode err:%i", err);
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, "I2S: RX channel in standard mode with 16 bit width on %i channel(s) initialized", slot_mode);
|
||||||
|
_i2s_on = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
AddLog(LOG_LEVEL_INFO, "I2S: invalid rx mode=%i", _rx_mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TasmotaI2S::updateClockConfig(void) {
|
||||||
|
i2s_channel_disable(_tx_handle);
|
||||||
|
i2s_std_clk_config_t clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(hertz);
|
||||||
|
#ifdef SOC_I2S_SUPPORTS_APLL
|
||||||
|
if (_apll) {
|
||||||
|
clk_cfg.clk_src = I2S_CLK_SRC_APLL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
int result = i2s_channel_reconfig_std_clock(_tx_handle, &clk_cfg );
|
||||||
|
if (_tx_running) { i2s_channel_enable(_tx_handle); }
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, "I2S: Updating clock config");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************************************************************************************\
|
||||||
|
* microphone related functions
|
||||||
|
\*********************************************************************************************/
|
||||||
|
|
||||||
|
uint32_t TasmotaI2S::I2sMicInit(void) {
|
||||||
|
if (!_rx_configured) { return 0; } // nothing configured
|
||||||
|
|
||||||
|
esp_err_t err = ESP_OK;
|
||||||
|
gpio_num_t clk_gpio;
|
||||||
|
|
||||||
|
i2s_slot_mode_t slot_mode = (_rx_channels == 1) ? I2S_SLOT_MODE_MONO : I2S_SLOT_MODE_STEREO;
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, "I2S: mic init rx_channels:%i rx_running:%i rx_handle:%p", slot_mode, _rx_running, _rx_handle);
|
||||||
|
|
||||||
|
// if (_tx_configured && _rx_running) { // duplex mode, mic was already initialized
|
||||||
|
// AddLog(LOG_LEVEL_DEBUG, "I2S: mic init exit Rx already enabled");
|
||||||
|
// return 0; // no need to en- or disable when in full duplex mode and already initialized
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (_rx_handle == nullptr){
|
||||||
|
// i2s_chan_config_t rx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
|
||||||
|
|
||||||
|
// AddLog(LOG_LEVEL_DEBUG, "I2S: rx_chan_cfg id:%i role:%i dma_desc_num:%i dma_frame_num:%i auto_clear:%i",
|
||||||
|
// rx_chan_cfg.id, rx_chan_cfg.role, rx_chan_cfg.dma_desc_num, rx_chan_cfg.dma_frame_num, rx_chan_cfg.auto_clear);
|
||||||
|
|
||||||
|
// err = i2s_new_channel(&rx_chan_cfg, NULL, &_rx_handle);
|
||||||
|
// AddLog(LOG_LEVEL_DEBUG, "I2S: mic init i2s_new_channel err=%i", err);
|
||||||
|
// switch (_rx_mode){
|
||||||
|
// case I2S_MODE_PDM:
|
||||||
|
// {
|
||||||
|
// i2s_pdm_rx_config_t rx_pdm_cfg = {
|
||||||
|
// .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(_rx_freq),
|
||||||
|
// /* The default mono slot is the left slot (whose 'select pin' of the PDM microphone is pulled down) */
|
||||||
|
// .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
|
||||||
|
// // .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, slot_mode),
|
||||||
|
// .gpio_cfg = {
|
||||||
|
// .clk = _gpio_ws,
|
||||||
|
// .din = _gpio_din,
|
||||||
|
// .invert_flags = {
|
||||||
|
// .clk_inv = _gpio_ws_inv,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// };
|
||||||
|
// if (_rx_slot_mask != I2S_SLOT_NOCHANGE) { rx_pdm_cfg.slot_cfg.slot_mask = (i2s_pdm_slot_mask_t)_rx_slot_mask; }
|
||||||
|
|
||||||
|
// // AddLog(LOG_LEVEL_DEBUG, "I2S: rx_pdm_cfg clk_cfg sample_rate_hz:%i clk_src:%i mclk_multiple:%i dn_sample_mode:%i",
|
||||||
|
// // rx_pdm_cfg.clk_cfg.sample_rate_hz, rx_pdm_cfg.clk_cfg.clk_src, rx_pdm_cfg.clk_cfg.mclk_multiple, rx_pdm_cfg.clk_cfg.dn_sample_mode);
|
||||||
|
|
||||||
|
// // AddLog(LOG_LEVEL_DEBUG, "I2S: rx_pdm_cfg slot_cfg data_bit_width:%i slot_bit_width:%i slot_mode:%i slot_mask:%i",
|
||||||
|
// // rx_pdm_cfg.slot_cfg.data_bit_width, rx_pdm_cfg.slot_cfg.slot_bit_width, rx_pdm_cfg.slot_cfg.slot_mode, rx_pdm_cfg.slot_cfg.slot_mask);
|
||||||
|
|
||||||
|
// // AddLog(LOG_LEVEL_DEBUG, "I2S: rx_pdm_cfg gpio_cfg clk:%i din:%i clk_inv:%i",
|
||||||
|
// // rx_pdm_cfg.gpio_cfg.clk, rx_pdm_cfg.gpio_cfg.din, rx_pdm_cfg.gpio_cfg.invert_flags.clk_inv);
|
||||||
|
|
||||||
|
// err = i2s_channel_init_pdm_rx_mode(_rx_handle, &rx_pdm_cfg);
|
||||||
|
// AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: RX channel in PDM mode, CLK: %i, DIN: %i, 16 bit width, %i channel(s), err code: 0x%04X"),
|
||||||
|
// _gpio_ws, _gpio_din, slot_mode, err);
|
||||||
|
// }
|
||||||
|
// break;
|
||||||
|
// case I2S_MODE_STD:
|
||||||
|
// {
|
||||||
|
// i2s_std_config_t rx_std_cfg = {
|
||||||
|
// .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(_rx_freq),
|
||||||
|
// .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, slot_mode),
|
||||||
|
// .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 = I2S_GPIO_UNUSED,
|
||||||
|
// .din = (gpio_num_t)Pin(GPIO_I2S_DIN),
|
||||||
|
// .invert_flags = {
|
||||||
|
// .mclk_inv = false,
|
||||||
|
// .bclk_inv = false,
|
||||||
|
// .ws_inv = false,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// };
|
||||||
|
// if (_rx_slot_mask != I2S_SLOT_NOCHANGE) { rx_std_cfg.slot_cfg.slot_mask = (i2s_std_slot_mask_t)_rx_slot_mask; }
|
||||||
|
|
||||||
|
// err = i2s_channel_init_std_mode(audio_i2s.rx_handle, &rx_std_cfg);
|
||||||
|
// AddLog(LOG_LEVEL_DEBUG, "I2S: RX i2s_channel_init_std_mode err:%i", err);
|
||||||
|
// AddLog(LOG_LEVEL_DEBUG, "I2S: RX channel in standard mode with 16 bit width on %i channel(s) initialized", slot_mode);
|
||||||
|
// }
|
||||||
|
// break;
|
||||||
|
// default:
|
||||||
|
// AddLog(LOG_LEVEL_INFO, "I2S: invalid rx mode=%i", _rx_mode);
|
||||||
|
// return -1;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (!_rx_running) {
|
||||||
|
err = i2s_channel_enable(_rx_handle);
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: RX channel enable err:0x%04X"),err);
|
||||||
|
_rx_running = true;
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void TasmotaI2S::I2sMicDeinit(void) {
|
||||||
|
esp_err_t err = ESP_OK;
|
||||||
|
gpio_num_t clk_gpio;
|
||||||
|
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, "I2S: mic deinit rx_running:%i rx_handle:%p", _rx_running, _rx_handle);
|
||||||
|
if (!_rx_handle) { return; }
|
||||||
|
|
||||||
|
if (!_tx_configured || !_rx_configured) { // if duplex mode, there is no mic channel - TODO check this
|
||||||
|
int err = i2s_channel_disable(_rx_handle);
|
||||||
|
i2s_del_channel(_rx_handle);
|
||||||
|
_rx_handle = nullptr;
|
||||||
|
_rx_running = false;
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, "I2S: RX channel disable: %i", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // USE_I2S_AUDIO
|
||||||
|
#endif // defined(ESP32) && ESP_IDF_VERSION_MAJOR >= 5
|
|
@ -1,428 +0,0 @@
|
||||||
/*
|
|
||||||
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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#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 <ESP8266SAM.h>
|
|
||||||
#include "AudioFileSourceFS.h"
|
|
||||||
#include "AudioGeneratorTalkie.h"
|
|
||||||
#include "AudioFileSourceICYStream.h"
|
|
||||||
#include "AudioFileSourceBuffer.h"
|
|
||||||
#include "AudioGeneratorAAC.h"
|
|
||||||
|
|
||||||
#include <layer3.h>
|
|
||||||
#include <types.h>
|
|
||||||
|
|
||||||
/*********************************************************************************************\
|
|
||||||
* Driver Settings in memory
|
|
||||||
\*********************************************************************************************/
|
|
||||||
|
|
||||||
// I2S communication mode
|
|
||||||
enum : int8_t {
|
|
||||||
I2S_MODE_STD = 0, // I2S mode standard
|
|
||||||
I2S_MODE_PDM = 1, // I2S mode PDM
|
|
||||||
I2S_MODE_TDM = 2, // I2S mode TDM
|
|
||||||
I2S_MODE_DAC = 3, // Using internal DAC - only available on ESP32
|
|
||||||
};
|
|
||||||
|
|
||||||
// I2S slot mask (left, right, both)
|
|
||||||
enum : int8_t {
|
|
||||||
I2S_SLOT_LEFT = 1, // left
|
|
||||||
I2S_SLOT_RIGHT = 2, // right
|
|
||||||
I2S_SLOT_BOTH = 3, // both
|
|
||||||
};
|
|
||||||
|
|
||||||
// I2S slot configuration
|
|
||||||
enum : int8_t {
|
|
||||||
I2S_SLOT_MSB = 0, // MSB
|
|
||||||
I2S_SLOT_PCM = 1, // PCM
|
|
||||||
I2S_SLOT_PHILIPS = 2, // Philips
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct{
|
|
||||||
struct{
|
|
||||||
uint8_t version = 0; // B00
|
|
||||||
|
|
||||||
// runtime options, will be saved but ignored on setting read
|
|
||||||
bool duplex = 0; // B01 - depends on GPIO setting and SOC caps, DIN and DOUT on same port in GPIO means -> try to use duplex if possible
|
|
||||||
bool tx = 0; // B02 - depends on GPIO setting
|
|
||||||
bool rx = 0; // B03 - depends on GPIO setting
|
|
||||||
uint32_t spare01; // B04-07
|
|
||||||
} sys;
|
|
||||||
struct {
|
|
||||||
uint8_t mode = I2S_MODE_STD; // B00 - I2S mode standard, PDM, TDM, DAC
|
|
||||||
bool apll = 1; // B01 - will be ignored on unsupported SOC's
|
|
||||||
uint8_t channels = 2; // B02 - 1 = mono, 2 = stereo
|
|
||||||
uint8_t codec = 0; // B03 - S3 box only, unused for now
|
|
||||||
uint8_t slot_config = I2S_SLOT_MSB;// B04 - slot configuration MSB = 0, PCM = 1, PHILIPS = 2
|
|
||||||
uint8_t volume = 10; // B05
|
|
||||||
bool mclk_inv = 0; // B06 - invert mclk
|
|
||||||
bool bclk_inv = 0; // B07 - invert bclk
|
|
||||||
bool ws_inv = 0; // B08 - invert ws
|
|
||||||
uint8_t spare[7]; // B09-0F
|
|
||||||
} tx;
|
|
||||||
struct {
|
|
||||||
uint32_t sample_rate = 16000; // B00-03
|
|
||||||
uint8_t gain = 30; // B04
|
|
||||||
uint8_t mode = I2S_MODE_STD; // B05 - I2S mode standard, PDM, TDM, DAC
|
|
||||||
uint8_t slot_mask = I2S_SLOT_LEFT;// B06 - slot mask
|
|
||||||
uint8_t slot_mode = 0; // B07 - mono/stereo - 1 is added for both
|
|
||||||
uint8_t codec = 0; // B08 - unused for now
|
|
||||||
uint8_t mp3_encoder = 1; // B09 - will be ignored without PS-RAM
|
|
||||||
uint8_t spare[6]; // B0A-0F
|
|
||||||
} 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 = nullptr;
|
|
||||||
|
|
||||||
TasmotaAudioOutputI2S *out = nullptr;
|
|
||||||
|
|
||||||
AudioFileSourceID3 *id3 = nullptr;
|
|
||||||
AudioGeneratorMP3 *decoder = NULL;
|
|
||||||
void *mp3ram = NULL;
|
|
||||||
|
|
||||||
TaskHandle_t mp3_task_handle;
|
|
||||||
TaskHandle_t mic_task_handle;
|
|
||||||
|
|
||||||
char mic_path[32];
|
|
||||||
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;
|
|
||||||
|
|
||||||
/*********************************************************************************************\
|
|
||||||
* 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() {
|
|
||||||
loadSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
~TasmotaAudioOutputI2S() {
|
|
||||||
this->stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------
|
|
||||||
// Setters for configuration parameters
|
|
||||||
//
|
|
||||||
// TODO: not sure we still need them since all this should be set at initialiation
|
|
||||||
// ------------------------------------------------------------------------------------------
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual bool SetRate(int hz) {
|
|
||||||
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: SetRate: %i was %i on=%i"), hz, this->hertz, _i2s_on);
|
|
||||||
if (hz == (int) this->hertz) { return true; }
|
|
||||||
this->hertz = hz;
|
|
||||||
if (_i2s_on) {
|
|
||||||
int result = updateClockConfig();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 uint8_t getTxChannels(void) const { return _tx_channels; }
|
|
||||||
inline bool getTxEnabled(void) const { return _tx_enabled; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------
|
|
||||||
// Setters
|
|
||||||
inline void setTxMode(uint8_t mode) { _tx_mode = mode; }
|
|
||||||
inline void setTxChannels(uint8_t channels) { _tx_channels = channels; }
|
|
||||||
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);
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
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 = I2S_MODE_STD; // EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2
|
|
||||||
bool _tx_enabled = false; // true = enabled, false = disabled
|
|
||||||
uint8_t _tx_channels = 2; // 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
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------
|
|
||||||
// Methods
|
|
||||||
// ------------------------------------------------------------------------------------------
|
|
||||||
void TasmotaAudioOutputI2S::loadSettings(void) {
|
|
||||||
hertz = 16000;
|
|
||||||
_i2s_on = false;
|
|
||||||
bps = I2S_DATA_BIT_WIDTH_16BIT;
|
|
||||||
_tx_channels = audio_i2s.Settings->tx.channels;
|
|
||||||
if (_tx_channels == 0) { _tx_channels = 2; } // if zero channel default to stereo
|
|
||||||
if (_tx_channels > 2) { _tx_channels = 2; } // if > 2 channels default to stereo
|
|
||||||
channels = (_tx_channels == 1) ? I2S_SLOT_MODE_MONO : I2S_SLOT_MODE_STEREO;
|
|
||||||
_tx_mode = I2S_MODE_STD;
|
|
||||||
_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_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);
|
|
||||||
if (result != 0){
|
|
||||||
AddLog(LOG_LEVEL_INFO, "I2S: Could not enable i2s_channel: %i", result);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
_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);
|
|
||||||
_i2s_on = false;
|
|
||||||
AddLog(LOG_LEVEL_INFO, "I2S: stop: I2S channel disabled");
|
|
||||||
}
|
|
||||||
_tx_enabled = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TasmotaAudioOutputI2S::ConsumeSample(int16_t sample[2]) {
|
|
||||||
return consumeSamples(sample, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
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; }
|
|
||||||
|
|
||||||
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_channels == 1) { // if mono, average the two samples
|
|
||||||
// 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 == I2S_MODE_DAC) {
|
|
||||||
left = Amplify(left) + 0x8000;
|
|
||||||
right = Amplify(right) + 0x8000;
|
|
||||||
} else {
|
|
||||||
left = Amplify(left);
|
|
||||||
right = Amplify(right);
|
|
||||||
}
|
|
||||||
|
|
||||||
ms[i*2 + LEFTCHANNEL] = left;
|
|
||||||
ms[i*2 + RIGHTCHANNEL] = right;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t i2s_bytes_written;
|
|
||||||
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; // 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)_gpio_din;
|
|
||||||
i2s_new_channel(&tx_chan_cfg, &_tx_chan, &audio_i2s.rx_handle);
|
|
||||||
} else{
|
|
||||||
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_mclk,
|
|
||||||
.bclk = _gpio_bclk,
|
|
||||||
.ws = _gpio_ws,
|
|
||||||
.dout = _gpio_dout,
|
|
||||||
.din = _DIN,
|
|
||||||
.invert_flags = {
|
|
||||||
.mclk_inv = _gpio_mclk_inv,
|
|
||||||
.bclk_inv = _gpio_bclk_inv,
|
|
||||||
.ws_inv = _gpio_ws_inv,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// change configuration if we are using PCM or PHILIPS
|
|
||||||
if (audio_i2s.Settings->tx.slot_config == I2S_SLOT_PCM) { // 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 == I2S_SLOT_PHILIPS) { // 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 bits width on %i channels initialized i2s_on=%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 _i2s_on;
|
|
||||||
}
|
|
||||||
|
|
||||||
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_enabled) { i2s_channel_enable(_tx_chan); }
|
|
||||||
AddLog(LOG_LEVEL_DEBUG, "I2S: Updating clock config");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // USE_I2S_AUDIO
|
|
||||||
#endif // defined(ESP32) && ESP_IDF_VERSION_MAJOR >= 5
|
|
|
@ -26,6 +26,7 @@
|
||||||
#define USE_I2S_SAY_TIME
|
#define USE_I2S_SAY_TIME
|
||||||
#define USE_I2S_RTTTL
|
#define USE_I2S_RTTTL
|
||||||
#define USE_I2S_WEBRADIO
|
#define USE_I2S_WEBRADIO
|
||||||
|
#define USE_I2S_DEBUG // remove before flight
|
||||||
|
|
||||||
// Macros used in audio sub-functions
|
// Macros used in audio sub-functions
|
||||||
#undef AUDIO_PWR_ON
|
#undef AUDIO_PWR_ON
|
||||||
|
@ -54,6 +55,9 @@ void I2sWebRadioStopPlaying(void);
|
||||||
|
|
||||||
const char kI2SAudio_Commands[] PROGMEM = "I2S|"
|
const char kI2SAudio_Commands[] PROGMEM = "I2S|"
|
||||||
"Gain|Play|Rec|MGain|Stop"
|
"Gain|Play|Rec|MGain|Stop"
|
||||||
|
#ifdef USE_I2S_DEBUG
|
||||||
|
"|Mic" // debug only
|
||||||
|
#endif // USE_I2S_DEBUG
|
||||||
#ifdef USE_I2S_WEBRADIO
|
#ifdef USE_I2S_WEBRADIO
|
||||||
"|WR"
|
"|WR"
|
||||||
#endif // USE_I2S_WEBRADIO
|
#endif // USE_I2S_WEBRADIO
|
||||||
|
@ -76,6 +80,9 @@ const char kI2SAudio_Commands[] PROGMEM = "I2S|"
|
||||||
|
|
||||||
void (* const I2SAudio_Command[])(void) PROGMEM = {
|
void (* const I2SAudio_Command[])(void) PROGMEM = {
|
||||||
&CmndI2SGain, &CmndI2SPlay, &CmndI2SMicRec, &CmndI2SMicGain, &CmndI2SStop,
|
&CmndI2SGain, &CmndI2SPlay, &CmndI2SMicRec, &CmndI2SMicGain, &CmndI2SStop,
|
||||||
|
#ifdef USE_I2S_DEBUG
|
||||||
|
&CmndI2SMic,
|
||||||
|
#endif // USE_I2S_DEBUG
|
||||||
#ifdef USE_I2S_WEBRADIO
|
#ifdef USE_I2S_WEBRADIO
|
||||||
&CmndI2SWebRadio,
|
&CmndI2SWebRadio,
|
||||||
#endif // USE_I2S_WEBRADIO
|
#endif // USE_I2S_WEBRADIO
|
||||||
|
@ -100,94 +107,6 @@ void (* const I2SAudio_Command[])(void) PROGMEM = {
|
||||||
* microphone related functions
|
* microphone related functions
|
||||||
\*********************************************************************************************/
|
\*********************************************************************************************/
|
||||||
|
|
||||||
uint32_t I2sMicInit(void) {
|
|
||||||
esp_err_t err = ESP_OK;
|
|
||||||
gpio_num_t clk_gpio;
|
|
||||||
|
|
||||||
i2s_slot_mode_t slot_mode = (audio_i2s.Settings->rx.slot_mode == 0) ? I2S_SLOT_MODE_MONO : I2S_SLOT_MODE_STEREO;
|
|
||||||
AddLog(LOG_LEVEL_DEBUG, "I2S: mic init rx_channels=%i", slot_mode);
|
|
||||||
|
|
||||||
if (audio_i2s.Settings->sys.duplex == 1 && audio_i2s.rx_handle){
|
|
||||||
return 0; // no need to en- or disable when in full duplex mode and already initialized
|
|
||||||
}
|
|
||||||
|
|
||||||
if (audio_i2s.rx_handle == nullptr){
|
|
||||||
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
|
|
||||||
|
|
||||||
err = i2s_new_channel(&chan_cfg, NULL, &audio_i2s.rx_handle);
|
|
||||||
AddLog(LOG_LEVEL_DEBUG, "I2S: mic init i2s_new_channel err=%i", err);
|
|
||||||
switch (audio_i2s.Settings->rx.mode){
|
|
||||||
case I2S_MODE_PDM:
|
|
||||||
{
|
|
||||||
clk_gpio = (gpio_num_t)Pin(GPIO_I2S_WS,1); //legacy setting for Core2, might be wrong
|
|
||||||
if (clk_gpio == -1){
|
|
||||||
clk_gpio = (gpio_num_t)Pin(GPIO_I2S_WS); //fallback to other port, might be wrong
|
|
||||||
}
|
|
||||||
i2s_pdm_rx_config_t pdm_rx_cfg = {
|
|
||||||
.clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(audio_i2s.Settings->rx.sample_rate),
|
|
||||||
/* The default mono slot is the left slot (whose 'select pin' of the PDM microphone is pulled down) */
|
|
||||||
.slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, slot_mode),
|
|
||||||
.gpio_cfg = {
|
|
||||||
.clk = clk_gpio,
|
|
||||||
.din = (gpio_num_t)Pin(GPIO_I2S_DIN),
|
|
||||||
.invert_flags = {
|
|
||||||
.clk_inv = false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
pdm_rx_cfg.slot_cfg.slot_mask = (i2s_pdm_slot_mask_t)audio_i2s.Settings->rx.slot_mask;
|
|
||||||
err = i2s_channel_init_pdm_rx_mode(audio_i2s.rx_handle, &pdm_rx_cfg);
|
|
||||||
AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: RX channel in PDM mode, CLK: %i, DIN: %i, 16 bit width, %i channel(s), err code: %u"),clk_gpio, Pin(GPIO_I2S_DIN), slot_mode, err);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case I2S_MODE_STD:
|
|
||||||
{
|
|
||||||
i2s_std_config_t rx_std_cfg = {
|
|
||||||
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(audio_i2s.Settings->rx.sample_rate),
|
|
||||||
.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, slot_mode),
|
|
||||||
.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 = I2S_GPIO_UNUSED,
|
|
||||||
.din = (gpio_num_t)Pin(GPIO_I2S_DIN),
|
|
||||||
.invert_flags = {
|
|
||||||
.mclk_inv = false,
|
|
||||||
.bclk_inv = false,
|
|
||||||
.ws_inv = false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
rx_std_cfg.slot_cfg.slot_mask = (i2s_std_slot_mask_t)audio_i2s.Settings->rx.slot_mask;
|
|
||||||
i2s_channel_init_std_mode(audio_i2s.rx_handle, &rx_std_cfg);
|
|
||||||
AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: RX channel in standard mode with 16 bit width on %i channel(s) initialized"),slot_mode);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
AddLog(LOG_LEVEL_INFO, "I2S: invalid rx mode=%i", audio_i2s.Settings->rx.mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = i2s_channel_enable(audio_i2s.rx_handle);
|
|
||||||
AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: RX channel enable: %i"),err);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void I2sMicDeinit(void) {
|
|
||||||
esp_err_t err = ESP_OK;
|
|
||||||
gpio_num_t clk_gpio;
|
|
||||||
|
|
||||||
if (!audio_i2s.rx_handle) { return; }
|
|
||||||
|
|
||||||
if (!audio_i2s.Settings->sys.duplex) { // if duplex mode, there is no mic channel - TODO check this
|
|
||||||
int err = i2s_channel_disable(audio_i2s.rx_handle);
|
|
||||||
i2s_del_channel(audio_i2s.rx_handle);
|
|
||||||
audio_i2s.rx_handle = nullptr;
|
|
||||||
AddLog(LOG_LEVEL_DEBUG, "I2S: RX channel disable: %i", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// micro to mp3 file or stream
|
// micro to mp3 file or stream
|
||||||
void I2sMicTask(void *arg){
|
void I2sMicTask(void *arg){
|
||||||
int8_t error = 0;
|
int8_t error = 0;
|
||||||
|
@ -226,14 +145,14 @@ void I2sMicTask(void *arg){
|
||||||
|
|
||||||
shine_set_config_mpeg_defaults(&config.mpeg);
|
shine_set_config_mpeg_defaults(&config.mpeg);
|
||||||
|
|
||||||
if (audio_i2s.Settings->rx.slot_mode == 0) {
|
if (audio_i2s.Settings->rx.channels == 1) {
|
||||||
config.mpeg.mode = MONO;
|
config.mpeg.mode = MONO;
|
||||||
} else {
|
} else {
|
||||||
config.mpeg.mode = STEREO;
|
config.mpeg.mode = STEREO;
|
||||||
}
|
}
|
||||||
config.mpeg.bitr = 128;
|
config.mpeg.bitr = 128;
|
||||||
config.wave.samplerate = audio_i2s.Settings->rx.sample_rate;
|
config.wave.samplerate = audio_i2s.Settings->rx.sample_rate;
|
||||||
config.wave.channels = (channels)(audio_i2s.Settings->rx.slot_mode + 1);
|
config.wave.channels = (channels)(audio_i2s.Settings->rx.channels);
|
||||||
|
|
||||||
if (shine_check_config(config.wave.samplerate, config.mpeg.bitr) < 0) {
|
if (shine_check_config(config.wave.samplerate, config.mpeg.bitr) < 0) {
|
||||||
error = 3;
|
error = 3;
|
||||||
|
@ -247,7 +166,7 @@ void I2sMicTask(void *arg){
|
||||||
}
|
}
|
||||||
|
|
||||||
samples_per_pass = shine_samples_per_pass(s);
|
samples_per_pass = shine_samples_per_pass(s);
|
||||||
bytesize = samples_per_pass * 2 * (audio_i2s.Settings->rx.slot_mode + 1);
|
bytesize = samples_per_pass * 2 * (audio_i2s.Settings->rx.channels);
|
||||||
|
|
||||||
buffer = (int16_t*)malloc(bytesize);
|
buffer = (int16_t*)malloc(bytesize);
|
||||||
if (!buffer) {
|
if (!buffer) {
|
||||||
|
@ -310,7 +229,7 @@ exit:
|
||||||
audio_i2s.client.stop();
|
audio_i2s.client.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
I2sMicDeinit();
|
audio_i2s.out->I2sMicDeinit();
|
||||||
audio_i2s.mic_stop = 0;
|
audio_i2s.mic_stop = 0;
|
||||||
audio_i2s.mic_error = error;
|
audio_i2s.mic_error = error;
|
||||||
AddLog(LOG_LEVEL_INFO, PSTR("mp3task result code: %d"), error);
|
AddLog(LOG_LEVEL_INFO, PSTR("mp3task result code: %d"), error);
|
||||||
|
@ -334,7 +253,7 @@ int32_t I2sRecordShine(char *path) {
|
||||||
if (audio_i2s.use_stream) {
|
if (audio_i2s.use_stream) {
|
||||||
stack = 8000;
|
stack = 8000;
|
||||||
}
|
}
|
||||||
I2sMicInit();
|
audio_i2s.out->I2sMicInit();
|
||||||
|
|
||||||
err = xTaskCreatePinnedToCore(I2sMicTask, "MIC", stack, NULL, 3, &audio_i2s.mic_task_handle, 1);
|
err = xTaskCreatePinnedToCore(I2sMicTask, "MIC", stack, NULL, 3, &audio_i2s.mic_task_handle, 1);
|
||||||
|
|
||||||
|
@ -347,7 +266,8 @@ int32_t I2sRecordShine(char *path) {
|
||||||
// error codes
|
// error codes
|
||||||
enum {
|
enum {
|
||||||
I2S_OK = 0,
|
I2S_OK = 0,
|
||||||
I2S_ERR_OUTPUT_NOT_CONFIGURE,
|
I2S_ERR_OUTPUT_NOT_CONFIGURED,
|
||||||
|
I2S_ERR_INPUT_NOT_CONFIGURED,
|
||||||
I2S_ERR_DECODER_IN_USE,
|
I2S_ERR_DECODER_IN_USE,
|
||||||
I2S_ERR_FILE_NOT_FOUND,
|
I2S_ERR_FILE_NOT_FOUND,
|
||||||
};
|
};
|
||||||
|
@ -369,7 +289,6 @@ void I2SSettingsLoad(const char * config_filename, bool erase) {
|
||||||
AddLog(LOG_LEVEL_ERROR, "I2S: ERROR memory allocation failed");
|
AddLog(LOG_LEVEL_ERROR, "I2S: ERROR memory allocation failed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!config_filename) { return; } // if no filename, use defaults
|
if (!config_filename) { return; } // if no filename, use defaults
|
||||||
|
|
||||||
#ifndef USE_UFILESYS
|
#ifndef USE_UFILESYS
|
||||||
|
@ -406,53 +325,20 @@ void I2SSettingsSave(const char * config_filename) {
|
||||||
* Driver init
|
* Driver init
|
||||||
\*********************************************************************************************/
|
\*********************************************************************************************/
|
||||||
|
|
||||||
//
|
|
||||||
// I2sCheckCfg
|
|
||||||
//
|
|
||||||
// Multiple checks
|
|
||||||
void I2sCheckCfg(void){
|
|
||||||
// din and dout must be configured on port 0 for full duplex
|
|
||||||
bool useDuplexMode = ((Pin(GPIO_I2S_DIN) != -1) && (Pin(GPIO_I2S_DOUT) != -1));
|
|
||||||
// AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: DIN %i , DOUT %i"),Pin(GPIO_I2S_DIN),Pin(GPIO_I2S_DOUT) );
|
|
||||||
|
|
||||||
if (useDuplexMode){
|
|
||||||
if (audio_i2s.Settings->rx.mode == I2S_MODE_PDM || audio_i2s.Settings->tx.mode == I2S_MODE_PDM ){
|
|
||||||
useDuplexMode = false;
|
|
||||||
AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: PDM forbids full duplex mode"));
|
|
||||||
}
|
|
||||||
audio_i2s.Settings->sys.duplex = useDuplexMode ? 1 : 0;
|
|
||||||
if(useDuplexMode){
|
|
||||||
AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: will try to use full duplex mode"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (Pin(GPIO_I2S_DIN) != -1 || Pin(GPIO_I2S_DIN, 1) != -1){ // micro could be port 0 or 1
|
|
||||||
audio_i2s.Settings->sys.rx = 1;
|
|
||||||
AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: RX config = mode: %i, channels: %i, gain: %i, sample rate: %i"), audio_i2s.Settings->rx.mode, (uint8_t)(audio_i2s.Settings->rx.slot_mode + 1), audio_i2s.Settings->rx.gain, audio_i2s.Settings->rx.sample_rate);
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
audio_i2s.Settings->sys.rx = 0;
|
|
||||||
audio_i2s.Settings->rx.mp3_encoder = 0; // do not allocate buffer
|
|
||||||
}
|
|
||||||
if (Pin(GPIO_I2S_DOUT) != -1){ // output is only supported on port 0
|
|
||||||
audio_i2s.Settings->sys.tx = 1;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
audio_i2s.Settings->sys.tx = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
AddLog(LOG_LEVEL_INFO, PSTR("I2S: init pins bclk=%d, ws=%d, dout=%d, mclk=%d, din=%d [tx=%i, rx=%i, duplex=%i]"),
|
|
||||||
Pin(GPIO_I2S_BCLK) , Pin(GPIO_I2S_WS), Pin(GPIO_I2S_DOUT), Pin(GPIO_I2S_MCLK), Pin(GPIO_I2S_DIN),
|
|
||||||
audio_i2s.Settings->sys.tx, audio_i2s.Settings->sys.tx, audio_i2s.Settings->sys.duplex);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// I2sInit
|
// I2sInit
|
||||||
//
|
//
|
||||||
// Initialize I2S driver for input and output
|
// Initialize I2S driver for input and output
|
||||||
void I2sInit(void) {
|
void I2sInit(void) {
|
||||||
|
int32_t gpio_din_0 = Pin(GPIO_I2S_DIN, 0);
|
||||||
|
int32_t gpio_din_1 = Pin(GPIO_I2S_DIN, 1);
|
||||||
|
int32_t gpio_dout_0 = Pin(GPIO_I2S_DOUT, 0);
|
||||||
|
int32_t gpio_dout_1 = Pin(GPIO_I2S_DOUT, 1);
|
||||||
|
int32_t gpio_ws_0 = Pin(GPIO_I2S_WS, 0);
|
||||||
|
|
||||||
// we need at least one pin configured
|
// we need at least one pin configured
|
||||||
if(Pin(GPIO_I2S_DIN) + Pin(GPIO_I2S_DIN,1) + Pin(GPIO_I2S_DOUT) + Pin(GPIO_I2S_DOUT,1) == -4){
|
// Note: in case of ESP32 DAC output we may have only WS_0 configured. DAC is only supported on port 0
|
||||||
|
if ((gpio_din_0 < 0) && (gpio_din_1 < 0) && (gpio_dout_0 < 0) && (gpio_dout_1 < 0) && (gpio_ws_0 < 0)) {
|
||||||
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: no pin configured"));
|
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: no pin configured"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -460,37 +346,204 @@ void I2sInit(void) {
|
||||||
I2SSettingsLoad("/.drvset042", false); // load configuration (no-erase)
|
I2SSettingsLoad("/.drvset042", false); // load configuration (no-erase)
|
||||||
if (!audio_i2s.Settings) { return; } // fatal error, could not allocate memory for configuration
|
if (!audio_i2s.Settings) { return; } // fatal error, could not allocate memory for configuration
|
||||||
|
|
||||||
// check configuration is valid
|
// detect if we need full-duplex on port 0
|
||||||
I2sCheckCfg();
|
bool duplex = false;
|
||||||
|
if ((gpio_din_0 >= 0) && (gpio_dout_0 >= 0)) {
|
||||||
// TODO
|
// conditions are potentially favorable for duplex
|
||||||
int result = 0;
|
if (audio_i2s.Settings->rx.mode == I2S_MODE_PDM || audio_i2s.Settings->tx.mode == I2S_MODE_PDM ){
|
||||||
if (audio_i2s.Settings->sys.rx == 1 && audio_i2s.Settings->sys.duplex == 0){
|
AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: PDM forbids full duplex mode, ignoring 'I2S DIN 1'"));
|
||||||
I2sMicDeinit();
|
gpio_din_0 = -1; // hence deconfigure DIN_0 which can't be used
|
||||||
|
} else {
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: will try to use full duplex mode"));
|
||||||
|
duplex = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(audio_i2s.Settings->sys.tx == 1){
|
// AddLog(LOG_LEVEL_INFO, PSTR("I2S: init pins bclk=%d, ws=%d, dout=%d, mclk=%d, din=%d"),
|
||||||
audio_i2s.out = new TasmotaAudioOutputI2S;
|
// Pin(GPIO_I2S_BCLK, 0) , Pin(GPIO_I2S_WS, 0), Pin(GPIO_I2S_DOUT, 0), Pin(GPIO_I2S_MCLK, 0), Pin(GPIO_I2S_DIN, 0));
|
||||||
int err = audio_i2s.out->startI2SChannel() ? 0 : 1;
|
|
||||||
result += err;
|
audio_i2s.Settings->sys.duplex = false;
|
||||||
|
audio_i2s.Settings->sys.tx = false;
|
||||||
|
audio_i2s.Settings->sys.rx = false;
|
||||||
|
audio_i2s.Settings->sys.exclusive = false;
|
||||||
|
bool exclusive = false; // signals that in/out have a shared GPIO and need to un/install driver before use
|
||||||
|
|
||||||
|
for (uint32_t port = 0; port < SOC_I2S_NUM; port++) {
|
||||||
|
int32_t bclk = Pin(GPIO_I2S_BCLK, port);
|
||||||
|
int32_t ws = Pin(GPIO_I2S_WS, port);
|
||||||
|
int32_t dout = Pin(GPIO_I2S_DOUT, port);
|
||||||
|
int32_t mclk = Pin(GPIO_I2S_MCLK, port);
|
||||||
|
int32_t din = Pin(GPIO_I2S_DIN, port);
|
||||||
|
bool tx = false; // is Tx enabled for this port
|
||||||
|
bool rx = false; // is Rx enabled for this port
|
||||||
|
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, "I2S: I2S%i bclk=%i, ws=%i, dout=%i, mclk=%i, din=%i", port, bclk, ws, dout, mclk, din);
|
||||||
|
|
||||||
|
// if neither input, nor output, nor DAC/ADC skip (WS could is only needed for DAC but supports only port 0)
|
||||||
|
if (din < 0 && dout < 0 && (ws < 0 || port !=0)) { continue; }
|
||||||
|
|
||||||
|
const char *err_msg = nullptr; // to save code, we indicate an error with a message configured
|
||||||
|
bool duplex = (din >= 0) && (dout >= 0);
|
||||||
|
bool dac_mode = false;
|
||||||
|
if (din >= 0 || dout >= 0) {
|
||||||
|
// we have regular I2S configuration
|
||||||
|
// do multiple checks
|
||||||
|
// 1. check that WS is configured
|
||||||
|
if (ws < 0) {
|
||||||
|
// WS may be shared between both ports, so if it is configured on port 0, we accept it on port 1
|
||||||
|
int32_t ws0 = Pin(GPIO_I2S_WS, 0);
|
||||||
|
if (ws0 >= 0) {
|
||||||
|
ws = ws0;
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, "I2S: I2S%i WS is shared, using WS from port 0 (%i)", port, ws);
|
||||||
|
exclusive = true;
|
||||||
}
|
}
|
||||||
|
if (ws < 0) {
|
||||||
|
err_msg = "no WS pin configured";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 2. check that DAC mode is not enabled for output(incompatible with DIN/DOUT)
|
||||||
|
else if (dout >= 0 && audio_i2s.Settings->tx.mode == I2S_MODE_DAC) {
|
||||||
|
err_msg = "DAC mode is not compatible with DOUT";
|
||||||
|
}
|
||||||
|
// 3. check that ADC mode is not enabled for output (incompatible with DIN/DOUT)
|
||||||
|
else if (din >= 0 && audio_i2s.Settings->rx.mode == I2S_MODE_DAC) {
|
||||||
|
err_msg = "ADC mode is not compatible with DIN";
|
||||||
|
}
|
||||||
|
// 4. check that output is not already configured
|
||||||
|
else if (dout >= 0 && audio_i2s.out) {
|
||||||
|
err_msg = "output already configured";
|
||||||
|
}
|
||||||
|
// 5. check that input is not already configured
|
||||||
|
else if (din >= 0 && audio_i2s.in) {
|
||||||
|
err_msg = "input already configured";
|
||||||
|
}
|
||||||
|
// 6. check that we don't try PDM on port 1
|
||||||
|
else if (port != 0 && din >= 0 && audio_i2s.Settings->rx.mode == I2S_MODE_PDM) {
|
||||||
|
err_msg = "PDM Rx is not supported";
|
||||||
|
}
|
||||||
|
// 7. check that we don't try PDM on port 1
|
||||||
|
else if (port != 0 && dout >= 0 && audio_i2s.Settings->tx.mode == I2S_MODE_PDM) {
|
||||||
|
err_msg = "PDM Tx is not supported";
|
||||||
|
}
|
||||||
|
// 8. check that we don't try full-duplex with PDM in either direction
|
||||||
|
else if (duplex && (audio_i2s.Settings->rx.mode == I2S_MODE_PDM || audio_i2s.Settings->tx.mode == I2S_MODE_PDM )) {
|
||||||
|
err_msg = "PDM forbids full duplex mode";
|
||||||
|
duplex = false;
|
||||||
|
din = -1; // deconfigure DIN_0 which can't be used
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dac_mode = true;
|
||||||
|
// no DIN/DOUT, try DAC mode
|
||||||
|
// 1. Check that tx.mode is I2S_MODE_DAC
|
||||||
|
if (audio_i2s.Settings->tx.mode != I2S_MODE_DAC) {
|
||||||
|
err_msg = "DAC mode is not enabled";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// is there any error?
|
||||||
|
if (err_msg) {
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, "I2S: Error: %s for I2S%i, skipping", err_msg, port);
|
||||||
|
continue; // skip this port
|
||||||
|
}
|
||||||
|
|
||||||
|
tx = (dout >= 0) || dac_mode;
|
||||||
|
rx = (din >= 0);
|
||||||
|
|
||||||
|
if (duplex) {
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: will try to use full duplex mode"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TasmotaI2S * i2s = new TasmotaI2S;
|
||||||
|
i2s->setPinout(bclk, ws, dout, mclk, din,
|
||||||
|
audio_i2s.Settings->sys.mclk_inv[0], audio_i2s.Settings->sys.bclk_inv[0],
|
||||||
|
audio_i2s.Settings->sys.ws_inv[0], audio_i2s.Settings->tx.apll);
|
||||||
|
i2s->setSlotConfig((i2s_port_t)port, audio_i2s.Settings->tx.slot_config, audio_i2s.Settings->rx.slot_config,
|
||||||
|
audio_i2s.Settings->tx.slot_mask, audio_i2s.Settings->rx.slot_mask);
|
||||||
|
if (tx) {
|
||||||
|
i2s->setTxMode(audio_i2s.Settings->tx.mode);
|
||||||
|
i2s->setTxChannels(audio_i2s.Settings->tx.channels);
|
||||||
|
i2s->setRate(audio_i2s.Settings->tx.sample_rate);
|
||||||
|
}
|
||||||
|
if (rx) {
|
||||||
|
i2s->setRxMode(audio_i2s.Settings->rx.mode);
|
||||||
|
i2s->setRxFreq(audio_i2s.Settings->rx.sample_rate);
|
||||||
|
i2s->setRxChannels(audio_i2s.Settings->rx.channels);
|
||||||
|
i2s->setRate(audio_i2s.Settings->rx.sample_rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i2s->startI2SChannel(tx, rx)) {
|
||||||
|
// succesful, register handlers
|
||||||
|
if (tx) {
|
||||||
|
audio_i2s.out = i2s;
|
||||||
|
audio_i2s.Settings->sys.tx = true;
|
||||||
|
}
|
||||||
|
if (rx) {
|
||||||
|
audio_i2s.in = i2s;
|
||||||
|
audio_i2s.Settings->sys.rx = true;
|
||||||
|
}
|
||||||
|
if (duplex) {
|
||||||
|
audio_i2s.Settings->sys.duplex = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// do we have exclusive mode?
|
||||||
|
audio_i2s.Settings->sys.exclusive = exclusive;
|
||||||
|
if (audio_i2s.out) { audio_i2s.out->setExclusive(exclusive); }
|
||||||
|
if (audio_i2s.in) { audio_i2s.in->setExclusive(exclusive); }
|
||||||
|
|
||||||
if(audio_i2s.out != nullptr){
|
if(audio_i2s.out != nullptr){
|
||||||
audio_i2s.out->SetGain(((float)audio_i2s.Settings->tx.volume / 100.0) * 4.0);
|
audio_i2s.out->SetGain(((float)audio_i2s.Settings->tx.gain / 100.0) * 4.0);
|
||||||
audio_i2s.out->begin();
|
audio_i2s.out->begin();
|
||||||
audio_i2s.out->stop();
|
audio_i2s.out->stop();
|
||||||
}
|
}
|
||||||
audio_i2s.mp3ram = nullptr;
|
audio_i2s.mp3ram = nullptr;
|
||||||
|
|
||||||
if(audio_i2s.Settings->rx.mp3_encoder == 1){
|
if (audio_i2s.Settings->rx.mp3_preallocate == 1){
|
||||||
|
// if (UsePSRAM()) {
|
||||||
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: will allocate buffer for mp3 encoder"));
|
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: will allocate buffer for mp3 encoder"));
|
||||||
if (UsePSRAM()) {
|
audio_i2s.mp3ram = special_malloc(preallocateCodecSize);
|
||||||
audio_i2s.mp3ram = heap_caps_malloc(preallocateCodecSize, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
// }
|
||||||
|
// else{
|
||||||
|
// audio_i2s.Settings->rx.mp3_preallocate = 0; // no PS-RAM -> no MP3 encoding
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
else{
|
AddLog(LOG_LEVEL_DEBUG, "I2S: I2sInit done");
|
||||||
audio_i2s.Settings->rx.mp3_encoder = 0; // no PS-RAM -> no MP3 encoding
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*********************************************************************************************\
|
||||||
|
* General functions
|
||||||
|
\*********************************************************************************************/
|
||||||
|
|
||||||
|
//
|
||||||
|
// I2SPrepareTx() -> int
|
||||||
|
//
|
||||||
|
// Prepare I2S for output, handle exclusive access if necessary
|
||||||
|
//
|
||||||
|
// Returns `I2S_OK` if ok to send to output or error code
|
||||||
|
int32_t I2SPrepareTx(void) {
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, "I2S: I2SPrepareTx out=%p", audio_i2s.out);
|
||||||
|
if (!audio_i2s.out) { return I2S_ERR_OUTPUT_NOT_CONFIGURED; }
|
||||||
|
|
||||||
|
if (audio_i2s.Settings->sys.exclusive) {
|
||||||
|
// TODO - deconfigure input driver
|
||||||
}
|
}
|
||||||
|
return I2S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// I2SPrepareRx() -> int
|
||||||
|
//
|
||||||
|
// Prepare I2S for input, handle exclusive access if necessary
|
||||||
|
//
|
||||||
|
// Returns `I2S_OK` if ok to record input or error code
|
||||||
|
int32_t I2SPrepareRx(void) {
|
||||||
|
if (!audio_i2s.in) return I2S_ERR_OUTPUT_NOT_CONFIGURED;
|
||||||
|
|
||||||
|
if (audio_i2s.Settings->sys.exclusive) {
|
||||||
|
// TODO - deconfigure input driver
|
||||||
|
}
|
||||||
|
return I2S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*********************************************************************************************\
|
/*********************************************************************************************\
|
||||||
|
@ -558,7 +611,8 @@ void I2sStopPlaying() {
|
||||||
//
|
//
|
||||||
// Returns I2S_error_t
|
// Returns I2S_error_t
|
||||||
int32_t I2SPlayMp3(const char *path) {
|
int32_t I2SPlayMp3(const char *path) {
|
||||||
if (!audio_i2s.out) return I2S_ERR_OUTPUT_NOT_CONFIGURE;
|
int32_t i2s_err = I2S_OK;
|
||||||
|
if ((i2s_err = I2SPrepareTx()) != I2S_OK) { return i2s_err; }
|
||||||
if (audio_i2s.decoder || audio_i2s.mp3) return I2S_ERR_DECODER_IN_USE;
|
if (audio_i2s.decoder || audio_i2s.mp3) return I2S_ERR_DECODER_IN_USE;
|
||||||
|
|
||||||
// check if the filename starts with '/', if not add it
|
// check if the filename starts with '/', if not add it
|
||||||
|
@ -598,7 +652,7 @@ void mp3_delete(void) {
|
||||||
|
|
||||||
void Say(char *text) {
|
void Say(char *text) {
|
||||||
|
|
||||||
if (!audio_i2s.out) return;
|
if (I2SPrepareTx()) { return; }
|
||||||
|
|
||||||
I2SAudioPower(true);
|
I2SAudioPower(true);
|
||||||
|
|
||||||
|
@ -615,12 +669,44 @@ void Say(char *text) {
|
||||||
* Commands
|
* Commands
|
||||||
\*********************************************************************************************/
|
\*********************************************************************************************/
|
||||||
|
|
||||||
|
void CmndI2SMic(void) {
|
||||||
|
if (I2SPrepareRx()) {
|
||||||
|
ResponseCmndChar("I2S Mic not configured");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t err = ESP_OK;
|
||||||
|
if (audio_i2s.decoder || audio_i2s.mp3) return;
|
||||||
|
audio_i2s.in->I2sMicInit();
|
||||||
|
if (audio_i2s.in->getRxRunning()) {
|
||||||
|
uint8_t buf[128];
|
||||||
|
|
||||||
|
size_t bytes_read = 0;
|
||||||
|
esp_err_t err = i2s_channel_read(audio_i2s.in->getRxHandle(), buf, sizeof(buf), &bytes_read, 0);
|
||||||
|
if (err == ESP_ERR_TIMEOUT) { err = ESP_OK; }
|
||||||
|
AddLog(LOG_LEVEL_INFO, "I2S: Mic (err:%i size:%i) %*_H", err, bytes_read, bytes_read, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
// err = xTaskCreatePinnedToCore(I2sMicTask, "MIC", stack, NULL, 3, &audio_i2s.mic_task_handle, 1);
|
||||||
|
|
||||||
|
ResponseCmndDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void CmndI2SStop(void) {
|
void CmndI2SStop(void) {
|
||||||
|
if (!I2SPrepareTx()) {
|
||||||
|
ResponseCmndChar("I2S output not configured");
|
||||||
|
return;
|
||||||
|
}
|
||||||
I2sStopPlaying();
|
I2sStopPlaying();
|
||||||
ResponseCmndDone();
|
ResponseCmndDone();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CmndI2SPlay(void) {
|
void CmndI2SPlay(void) {
|
||||||
|
if (I2SPrepareTx()) {
|
||||||
|
ResponseCmndChar("I2S output not configured");
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (XdrvMailbox.data_len > 0) {
|
if (XdrvMailbox.data_len > 0) {
|
||||||
int32_t err = I2SPlayMp3(XdrvMailbox.data);
|
int32_t err = I2SPlayMp3(XdrvMailbox.data);
|
||||||
// display return message
|
// display return message
|
||||||
|
@ -628,7 +714,7 @@ void CmndI2SPlay(void) {
|
||||||
case I2S_OK:
|
case I2S_OK:
|
||||||
ResponseCmndDone();
|
ResponseCmndDone();
|
||||||
break;
|
break;
|
||||||
case I2S_ERR_OUTPUT_NOT_CONFIGURE:
|
case I2S_ERR_OUTPUT_NOT_CONFIGURED:
|
||||||
ResponseCmndChar("I2S output not configured");
|
ResponseCmndChar("I2S output not configured");
|
||||||
break;
|
break;
|
||||||
case I2S_ERR_DECODER_IN_USE:
|
case I2S_ERR_DECODER_IN_USE:
|
||||||
|
@ -649,14 +735,18 @@ void CmndI2SPlay(void) {
|
||||||
void CmndI2SGain(void) {
|
void CmndI2SGain(void) {
|
||||||
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
|
||||||
if (audio_i2s.out) {
|
if (audio_i2s.out) {
|
||||||
audio_i2s.Settings->tx.volume = XdrvMailbox.payload;
|
audio_i2s.Settings->tx.gain = XdrvMailbox.payload;
|
||||||
audio_i2s.out->SetGain(((float)(audio_i2s.Settings->tx.volume-2)/100.0)*4.0);
|
audio_i2s.out->SetGain(((float)(audio_i2s.Settings->tx.gain-2)/100.0)*4.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ResponseCmndNumber(audio_i2s.Settings->tx.volume);
|
ResponseCmndNumber(audio_i2s.Settings->tx.gain);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CmndI2SSay(void) {
|
void CmndI2SSay(void) {
|
||||||
|
if (I2SPrepareTx()) {
|
||||||
|
ResponseCmndChar("I2S output not configured");
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (XdrvMailbox.data_len > 0) {
|
if (XdrvMailbox.data_len > 0) {
|
||||||
Say(XdrvMailbox.data);
|
Say(XdrvMailbox.data);
|
||||||
}
|
}
|
||||||
|
@ -664,6 +754,10 @@ void CmndI2SSay(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void CmndI2SI2SRtttl(void) {
|
void CmndI2SI2SRtttl(void) {
|
||||||
|
if (I2SPrepareTx()) {
|
||||||
|
ResponseCmndChar("I2S output not configured");
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (XdrvMailbox.data_len > 0) {
|
if (XdrvMailbox.data_len > 0) {
|
||||||
Rtttl(XdrvMailbox.data);
|
Rtttl(XdrvMailbox.data);
|
||||||
}
|
}
|
||||||
|
@ -671,7 +765,7 @@ void CmndI2SI2SRtttl(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void CmndI2SMicRec(void) {
|
void CmndI2SMicRec(void) {
|
||||||
if (audio_i2s.Settings->rx.mp3_encoder == 1) {
|
if (audio_i2s.Settings->rx.mp3_preallocate == 1) {
|
||||||
if (XdrvMailbox.data_len > 0) {
|
if (XdrvMailbox.data_len > 0) {
|
||||||
if (!strncmp(XdrvMailbox.data, "-?", 2)) {
|
if (!strncmp(XdrvMailbox.data, "-?", 2)) {
|
||||||
Response_P("{\"I2SREC-duration\":%d}", audio_i2s.recdur);
|
Response_P("{\"I2SREC-duration\":%d}", audio_i2s.recdur);
|
||||||
|
@ -691,7 +785,7 @@ if (audio_i2s.Settings->rx.mp3_encoder == 1) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
if(audio_i2s.Settings->sys.rx == 1){
|
if (audio_i2s.in){
|
||||||
ResponseCmndChar_P(PSTR("need PSRAM for MP3 recording"));
|
ResponseCmndChar_P(PSTR("need PSRAM for MP3 recording"));
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
|
@ -702,6 +796,7 @@ else{
|
||||||
|
|
||||||
// mic gain in factor not percent
|
// mic gain in factor not percent
|
||||||
void CmndI2SMicGain(void) {
|
void CmndI2SMicGain(void) {
|
||||||
|
// TODO - does nothing for now
|
||||||
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 256)) {
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 256)) {
|
||||||
audio_i2s.Settings->rx.gain = XdrvMailbox.payload;
|
audio_i2s.Settings->rx.gain = XdrvMailbox.payload;
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,21 +40,22 @@ extern "C" {
|
||||||
|
|
||||||
// AudioOutputI2S.init() -> instance
|
// AudioOutputI2S.init() -> instance
|
||||||
void* be_audio_output_i2s_init(void) {
|
void* be_audio_output_i2s_init(void) {
|
||||||
return audio_i2s.out; // return the singleton of TasmotaAudioOutputI2S which is already initialized
|
return audio_i2s.out; // return the singleton of TasmotaI2S which is already initialized
|
||||||
}
|
}
|
||||||
|
|
||||||
// AudioOutputI2S.deinit()-> void
|
// AudioOutputI2S.deinit()-> void
|
||||||
void be_audio_output_i2s_deinit(TasmotaAudioOutputI2S * out) {
|
void be_audio_output_i2s_deinit(TasmotaI2S * out) {
|
||||||
out->stop();
|
out->stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
// AudioOutputI2S.begin() -> bool
|
// AudioOutputI2S.begin() -> bool
|
||||||
int be_audio_output_i2s_begin(TasmotaAudioOutputI2S* out) {
|
int be_audio_output_i2s_begin(TasmotaI2S* out) {
|
||||||
|
if (I2SPrepareTx()) { return false; }
|
||||||
return out->begin();
|
return out->begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
// AudioOutputI2S.stop() -> bool
|
// AudioOutputI2S.stop() -> bool
|
||||||
int be_audio_output_i2s_stop(TasmotaAudioOutputI2S* out) {
|
int be_audio_output_i2s_stop(TasmotaI2S* out) {
|
||||||
return out->stop();
|
return out->stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +126,7 @@ extern "C" {
|
||||||
// AudioOutputI2S()
|
// AudioOutputI2S()
|
||||||
//
|
//
|
||||||
int i2s_output_i2s_init(bvm *vm) {
|
int i2s_output_i2s_init(bvm *vm) {
|
||||||
TasmotaAudioOutputI2S * audio = new TasmotaAudioOutputI2S();
|
TasmotaI2S * audio = new TasmotaI2S();
|
||||||
be_pushcomptr(vm, (void*) audio);
|
be_pushcomptr(vm, (void*) audio);
|
||||||
be_setmember(vm, 1, ".p");
|
be_setmember(vm, 1, ".p");
|
||||||
be_return_nil(vm);
|
be_return_nil(vm);
|
||||||
|
@ -134,7 +135,7 @@ extern "C" {
|
||||||
int i2s_output_i2s_deinit(bvm *vm) {
|
int i2s_output_i2s_deinit(bvm *vm) {
|
||||||
int argc = be_top(vm);
|
int argc = be_top(vm);
|
||||||
be_getmember(vm, 1, ".p");
|
be_getmember(vm, 1, ".p");
|
||||||
TasmotaAudioOutputI2S * audio = (TasmotaAudioOutputI2S *) be_tocomptr(vm, -1);
|
TasmotaI2S * audio = (TasmotaI2S *) be_tocomptr(vm, -1);
|
||||||
if (audio) {
|
if (audio) {
|
||||||
delete audio;
|
delete audio;
|
||||||
// clear
|
// clear
|
||||||
|
@ -177,6 +178,7 @@ extern "C" {
|
||||||
int i2s_generator_wav_begin(bvm *vm) {
|
int i2s_generator_wav_begin(bvm *vm) {
|
||||||
int argc = be_top(vm);
|
int argc = be_top(vm);
|
||||||
if (argc > 2) {
|
if (argc > 2) {
|
||||||
|
if (I2SPrepareTx()) { be_raisef(vm, "internal_error", "I2SPrepareTx() failed"); be_return_nil(vm); }
|
||||||
AudioGeneratorWAV * wav = i2s_generator_wav_get(vm);
|
AudioGeneratorWAV * wav = i2s_generator_wav_get(vm);
|
||||||
be_getmember(vm, 2, ".p");
|
be_getmember(vm, 2, ".p");
|
||||||
AudioFileSource * source = (AudioFileSource*) be_tocomptr(vm, -1);
|
AudioFileSource * source = (AudioFileSource*) be_tocomptr(vm, -1);
|
||||||
|
@ -244,6 +246,7 @@ extern "C" {
|
||||||
int i2s_generator_mp3_begin(bvm *vm) {
|
int i2s_generator_mp3_begin(bvm *vm) {
|
||||||
int argc = be_top(vm);
|
int argc = be_top(vm);
|
||||||
if (argc > 2) {
|
if (argc > 2) {
|
||||||
|
if (I2SPrepareTx()) { be_raisef(vm, "internal_error", "I2SPrepareTx() failed"); be_return_nil(vm); }
|
||||||
AudioGeneratorMP3 * mp3 = i2s_generator_mp3_get(vm);
|
AudioGeneratorMP3 * mp3 = i2s_generator_mp3_get(vm);
|
||||||
be_getmember(vm, 2, ".p");
|
be_getmember(vm, 2, ".p");
|
||||||
AudioFileSource * source = (AudioFileSource*) be_tocomptr(vm, -1);
|
AudioFileSource * source = (AudioFileSource*) be_tocomptr(vm, -1);
|
||||||
|
@ -313,12 +316,99 @@ extern "C" {
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
// AudioInputI2S.init() -> instance
|
// AudioInputI2S.init() -> instance
|
||||||
void* be_audio_input_i2s_init(void) {
|
void* be_audio_input_i2s_init(void) {
|
||||||
return audio_i2s.out; // return the singleton of TasmotaAudioOutputI2S which is already initialized
|
return audio_i2s.in; // return the singleton of TasmotaI2S which is already initialized
|
||||||
}
|
}
|
||||||
|
|
||||||
// AudioInputI2S.deinit()-> void
|
// AudioInputI2S.deinit()-> void
|
||||||
void be_audio_input_i2s_deinit(TasmotaAudioOutputI2S * out) {
|
void be_audio_input_i2s_deinit(TasmotaI2S * in) {
|
||||||
out->stop();
|
in->stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// AudioInputI2S.begin() -> bool
|
||||||
|
int be_audio_input_i2s_begin(bvm *vm, TasmotaI2S* in) {
|
||||||
|
if (I2SPrepareRx()) { be_raisef(vm, "internal_error", "I2SPrepareRx() failed"); be_return_nil(vm); }
|
||||||
|
in->I2sMicInit();
|
||||||
|
return in->getRxRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
// AudioInputI2S.stop() -> bool
|
||||||
|
int be_audio_input_i2s_stop(TasmotaI2S* in) {
|
||||||
|
in->I2sMicDeinit();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// AudioInputI2S.get_rate() -> int
|
||||||
|
int be_audio_input_i2s_get_rate(TasmotaI2S* in) {
|
||||||
|
return in->getRxRate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// AudioInputI2S.get_bits_per_sample() -> int
|
||||||
|
int be_audio_input_i2s_get_bits_per_sample(TasmotaI2S* in) {
|
||||||
|
return in->getRxBitsPerSample();
|
||||||
|
}
|
||||||
|
|
||||||
|
// AudioInputI2S.get_channels() -> int
|
||||||
|
int be_audio_input_i2s_get_channels(TasmotaI2S* in) {
|
||||||
|
return in->getRxChannels();
|
||||||
|
}
|
||||||
|
|
||||||
|
// AudioInputI2S.get_gain() -> real
|
||||||
|
float be_audio_input_i2s_get_gain(TasmotaI2S* in) {
|
||||||
|
return in->getRxGain();
|
||||||
|
}
|
||||||
|
|
||||||
|
// AudioInputI2S.set_gain(gain:real) -> bool
|
||||||
|
int be_audio_input_set_gain(TasmotaI2S* in, float gain) {
|
||||||
|
return true; // TODO
|
||||||
|
// return in->SetGain(gain);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// AudioInputI2S.read_bytes() -> bytes()
|
||||||
|
//
|
||||||
|
// Reads bytes in the input buffer
|
||||||
|
// Pre-condition: microphone but be recording (call begin() first)
|
||||||
|
//
|
||||||
|
// Returns `nil` if not configured or buffer empty
|
||||||
|
// Returns `bytes()` instance with 16-bits audio
|
||||||
|
int be_audio_input_i2s_read_bytes(bvm *vm) {
|
||||||
|
int argc = be_top(vm);
|
||||||
|
if (!audio_i2s.in->getRxRunning()) { be_return_nil(vm); }
|
||||||
|
|
||||||
|
uint16_t buf_audio[256];
|
||||||
|
|
||||||
|
size_t bytes_read = 0;
|
||||||
|
esp_err_t err = i2s_channel_read(audio_i2s.in->getRxHandle(), buf_audio, sizeof(buf_audio), &bytes_read, 0);
|
||||||
|
// AddLog(LOG_LEVEL_DEBUG, "BRY: be_audio_input_i2s_read_bytes - err=%i bytes=%i", err, bytes_read);
|
||||||
|
if ((err != ESP_OK) && (err != ESP_ERR_TIMEOUT)) { be_return_nil(vm); }
|
||||||
|
if (bytes_read == 0) { be_return_nil(vm); }
|
||||||
|
|
||||||
|
// we received some data
|
||||||
|
if (argc >= 2 && be_isbytes(vm, 2)) {
|
||||||
|
// we have already a bytes() buffer
|
||||||
|
be_pushvalue(vm, 2); // push on top
|
||||||
|
// resize to btr
|
||||||
|
be_getmember(vm, -1, "resize");
|
||||||
|
be_pushvalue(vm, -2);
|
||||||
|
be_pushint(vm, bytes_read);
|
||||||
|
be_call(vm, 2);
|
||||||
|
be_pop(vm, 3);
|
||||||
|
} else {
|
||||||
|
be_pushbytes(vm, nullptr, bytes_read); // allocate a buffer of size btr filled with zeros
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the address of the buffer
|
||||||
|
be_getmember(vm, -1, "_buffer");
|
||||||
|
be_pushvalue(vm, -2);
|
||||||
|
be_call(vm, 1);
|
||||||
|
uint8_t * buf = (uint8_t*) be_tocomptr(vm, -2);
|
||||||
|
be_pop(vm, 2);
|
||||||
|
|
||||||
|
// copy
|
||||||
|
memmove(buf, buf_audio, bytes_read);
|
||||||
|
|
||||||
|
be_return(vm); /* return code */
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,7 +136,7 @@ extern "C" {
|
||||||
be_setmember(vm, 1, "remote_port");
|
be_setmember(vm, 1, "remote_port");
|
||||||
be_pop(vm, 1);
|
be_pop(vm, 1);
|
||||||
|
|
||||||
be_return(vm); /* return code */
|
be_return(vm);
|
||||||
} else {
|
} else {
|
||||||
be_return_nil(vm);
|
be_return_nil(vm);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue