Minor refactoring of audio code for Arduino3 (#19559)

This commit is contained in:
s-hadinger 2023-09-21 09:00:28 +02:00 committed by GitHub
parent fc513af351
commit 7de25acac0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 504 additions and 477 deletions

View File

@ -9,134 +9,80 @@
#ifdef USE_I2S_AUDIO_BERRY
#include "be_mapping.h"
#include "AudioOutput.h"
extern "C" void berry_log_C(const char * berry_buf, ...);
extern int i2s_output_i2s_init(bvm *vm);
extern int i2s_output_i2s_deinit(bvm *vm);
extern int i2s_output_i2s_stop(bvm *vm);
extern "C" {
extern int i2s_output_i2s_init(bvm *vm);
extern int i2s_output_i2s_deinit(bvm *vm);
extern int i2s_output_i2s_stop(bvm *vm);
extern int i2s_generator_wav_init(bvm *vm);
extern int i2s_generator_wav_deinit(bvm *vm);
extern int i2s_generator_wav_begin(bvm *vm);
extern int i2s_generator_wav_loop(bvm *vm);
extern int i2s_generator_wav_stop(bvm *vm);
extern int i2s_generator_wav_isrunning(bvm *vm);
extern int i2s_generator_wav_init(bvm *vm);
extern int i2s_generator_wav_deinit(bvm *vm);
extern int i2s_generator_wav_begin(bvm *vm);
extern int i2s_generator_wav_loop(bvm *vm);
extern int i2s_generator_wav_stop(bvm *vm);
extern int i2s_generator_wav_isrunning(bvm *vm);
extern int i2s_generator_mp3_init(bvm *vm);
extern int i2s_generator_mp3_deinit(bvm *vm);
extern int i2s_generator_mp3_begin(bvm *vm);
extern int i2s_generator_mp3_loop(bvm *vm);
extern int i2s_generator_mp3_stop(bvm *vm);
extern int i2s_generator_mp3_isrunning(bvm *vm);
extern int i2s_generator_mp3_init(bvm *vm);
extern int i2s_generator_mp3_deinit(bvm *vm);
extern int i2s_generator_mp3_begin(bvm *vm);
extern int i2s_generator_mp3_loop(bvm *vm);
extern int i2s_generator_mp3_stop(bvm *vm);
extern int i2s_generator_mp3_isrunning(bvm *vm);
#ifdef USE_UFILESYS
extern int i2s_file_source_fs_init(bvm *vm);
extern int i2s_file_source_fs_deinit(bvm *vm);
extern int i2s_file_source_fs_init(bvm *vm);
extern int i2s_file_source_fs_deinit(bvm *vm);
#endif // USE_UFILESYS
}
// AudioOutput.set_rate(rate_hz:int) -> bool
AudioOutput* be_audio_output_init(void) {
return new AudioOutput();
}
extern void* be_audio_output_init(void);
BE_FUNC_CTYPE_DECLARE(be_audio_output_init, "+.p", "");
// AudioOutput.set_rate(rate_hz:int) -> bool
int be_audio_output_set_rate(AudioOutput* out, int hz) {
return out->SetRate(hz);
}
extern int be_audio_output_set_rate(void* out, int hz);
BE_FUNC_CTYPE_DECLARE(be_audio_output_set_rate, "b", ".i");
// AudioOutput.set_bits_per_sample(bits_per_sample:int) -> bool
int be_audio_output_set_bits_per_sample(AudioOutput* out, int bps) {
return out->SetBitsPerSample(bps);
}
extern int be_audio_output_set_bits_per_sample(void* out, int bps);
BE_FUNC_CTYPE_DECLARE(be_audio_output_set_bits_per_sample, "b", ".i");
// AudioOutput.set_channels(channels:int) -> bool
int be_audio_output_set_channels(AudioOutput* out, int channels) {
return out->SetChannels(channels);
}
extern int be_audio_output_set_channels(void* out, int channels);
BE_FUNC_CTYPE_DECLARE(be_audio_output_set_channels, "b", ".i");
// AudioOutput.set_gain(gain:real) -> bool
int be_audio_output_set_gain(AudioOutput* out, float gain) {
return out->SetGain(gain);
}
extern int be_audio_output_set_gain(void* out, float gain);
BE_FUNC_CTYPE_DECLARE(be_audio_output_set_gain, "b", ".f");
// AudioOutput.begin() -> bool
int be_audio_output_begin(AudioOutput* out) {
return out->begin();
}
extern int be_audio_output_begin(void* out);
BE_FUNC_CTYPE_DECLARE(be_audio_output_begin, "b", ".");
// AudioOutput.stop() -> bool
int be_audio_output_stop(AudioOutput* out) {
return out->stop();
}
extern int be_audio_output_stop(void* out);
BE_FUNC_CTYPE_DECLARE(be_audio_output_stop, "b", ".");
// AudioOutput.flush() -> bool
void be_audio_output_flush(AudioOutput* out) {
out->flush();
}
extern void be_audio_output_flush(void* out);
BE_FUNC_CTYPE_DECLARE(be_audio_output_flush, "", ".");
// AudioOutput.consume_mono(bytes) -> int
int be_audio_output_consume_mono(AudioOutput* out, uint16_t *pcm, int bytes_len, int index) {
int pcm_len = bytes_len / 2;
int n;
// berry_log_C("be_audio_output_consume_mono_ntv out=%p pcm=%p bytes_len=%i index=%i", out, pcm, bytes_len, index);
for (n = 0; index + n < pcm_len; n++) {
int16_t ms[2];
ms[AudioOutput::LEFTCHANNEL] = ms[AudioOutput::RIGHTCHANNEL] = pcm[index + n];
if (!out->ConsumeSample(ms)) { break; }
}
return n;
}
extern int be_audio_output_consume_mono(void* out, uint16_t *pcm, int bytes_len, int index);
BE_FUNC_CTYPE_DECLARE(be_audio_output_consume_mono, "i", ".(bytes)~i");
// AudioOutput.consume_stereo(bytes) -> int
int be_audio_output_consume_stereo(AudioOutput* out, uint16_t *pcm, int bytes_len, int index) {
int pcm_len = bytes_len / 4; // 2 samples LEFT+RIGHT of 2 bytes each
int n;
// berry_log_C("be_audio_output_consume_stereo_ntv out=%p pcm=%p bytes_len=%i index=%i", out, pcm, bytes_len, index);
for (n = 0; index + n < pcm_len; n++) {
int16_t ms[2];
ms[AudioOutput::LEFTCHANNEL] = pcm[index + n + n];
ms[AudioOutput::RIGHTCHANNEL] = pcm[index + n + n + 1];
if (!out->ConsumeSample(ms)) { break; }
}
return n;
}
extern int be_audio_output_consume_stereo(void* out, uint16_t *pcm, int bytes_len, int index);
BE_FUNC_CTYPE_DECLARE(be_audio_output_consume_stereo, "i", ".(bytes)~i");
// AudioOutput.consume_silence() -> int, push silence frames
int be_audio_output_consume_silence(AudioOutput* out) {
int n = 0;
int16_t ms[2] = {0, 0};
while (true) {
if (!out->ConsumeSample(ms)) { break; }
n++;
}
return n;
}
extern int be_audio_output_consume_silence(void* out);
BE_FUNC_CTYPE_DECLARE(be_audio_output_consume_silence, "i", ".");
#include "AudioOutputI2S.h"
// AudioOutputI2S.set_lsb_justified(gain:real) -> nil
int i2s_output_i2s_set_lsb_justified(AudioOutputI2S* out, bbool lsbJustified) {
return out->SetLsbJustified(lsbJustified);
}
extern int i2s_output_i2s_set_lsb_justified(void* out, bbool lsbJustified);
BE_FUNC_CTYPE_DECLARE(i2s_output_i2s_set_lsb_justified, "b", ".b");
extern "C" {
#include "be_fixed_be_class_AudioOutput.h"
#include "be_fixed_be_class_AudioOutputI2S.h"
#include "be_fixed_be_class_AudioGenerator.h"
@ -145,7 +91,6 @@ extern "C" {
#include "be_fixed_be_class_AudioFileSource.h"
#include "be_fixed_be_class_AudioFileSourceFS.h"
}
/* @const_object_info_begin
class be_class_AudioOutput (scope: global, name: AudioOutput, strings: weak) {
@ -175,15 +120,9 @@ class be_class_AudioFileSource (scope: global, name: AudioFileSource, strings: w
}
class be_class_AudioOutputI2S (scope: global, name: AudioOutputI2S, super: be_class_AudioOutput, strings: weak) {
EXTERNAL_I2S, int(AudioOutputI2S::EXTERNAL_I2S)
INTERNAL_DAC, int(AudioOutputI2S::INTERNAL_DAC)
INTERNAL_PDM, int(AudioOutputI2S::INTERNAL_PDM)
init, func(i2s_output_i2s_init)
deinit, func(i2s_output_i2s_deinit)
stop, func(i2s_output_i2s_stop)
set_lsb_justified, ctype_func(i2s_output_i2s_set_lsb_justified)
}
class be_class_AudioGeneratorWAV (scope: global, name: AudioGeneratorWAV, super: be_class_AudioGenerator, strings: weak) {

View File

@ -0,0 +1,350 @@
/*
xdrv_42_0_i2s__lib_idf51.ino - Simplified Audio library
Copyright (C) 2021 Gerhard Mutz, Theo Arends, Staars, Stephan Hadinger
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <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>
#undef AUDIO_PWR_ON
#undef AUDIO_PWR_OFF
#define AUDIO_PWR_ON I2SAudioPower(true);
#define AUDIO_PWR_OFF I2SAudioPower(false);
#define USE_I2S_SAY
#define USE_I2S_SAY_TIME
#define USE_I2S_RTTTL
/*********************************************************************************************\
* Driver Settings in memory
\*********************************************************************************************/
typedef struct{
struct{
uint32_t version : 8 = 0;
// runtime options, will be saved but ignored on setting read
uint32_t duplex : 1 = 0; // depends on GPIO setting and SOC caps, DIN and DOUT on same port in GPIO means -> try to use duplex if possible
uint32_t tx : 1 = 0; // depends on GPIO setting
uint32_t rx : 1 = 0; // depends on GPIO setting
} sys;
struct {
uint32_t mode : 2 = 0; // bit 0+1 STD = 0, PDM = 1, TDM = 2
uint32_t apll : 1 = 1; // bit 2 - will be ignored on unsupported SOC's
uint32_t mono : 1 = 0; // bit 3 0 = stereo, 1 = mono
uint32_t codec : 1 = 0; // bit 4 - S3 box only, unused for now
uint32_t webradio : 1 = 1; // bit 5 - allocate buffer for webradio
uint32_t spare01 : 1 = 1; // bit 6 - request duplex, means RX and TX on 1 slot
uint32_t lsbJustified : 1; // bit 7 - allow supporting LSBJ chips, e.g. TM8211/PT8211
uint32_t volume : 8 = 10; // bit 8-15
uint32_t spare02 : 16; // bit 16-31
} tx;
struct {
struct{
uint16_t sample_rate = 32000;
uint8_t gain = 30;
uint8_t mode = 0; //STD = 0, PDM = 1, TDM = 2
uint8_t slot_mask : 2 = 1; // left = 1 /right = 2 /both = 3
uint8_t slot_mode : 1 = 0; // mono/stereo - 1 is added for both
uint8_t codec : 1 = 0; // unused for now
uint8_t mp3_encoder : 1 = 1; // will be ignored without PS-RAM
};
} rx;
} tI2SSettings;
typedef union {
uint8_t data;
struct {
uint8_t master : 1;
uint8_t enabled : 1;
uint8_t swap_mic : 1;
uint8_t mode : 2;
};
} BRIDGE_MODE;
class TasmotaAudioOutputI2S;
struct AUDIO_I2S_t {
tI2SSettings *Settings;
i2s_chan_handle_t rx_handle = nullptr;
AudioGeneratorMP3 *mp3 = nullptr;
AudioFileSourceFS *file;
TasmotaAudioOutputI2S *out;
AudioFileSourceID3 *id3;
AudioGeneratorMP3 *decoder = NULL;
void *mp3ram = NULL;
// Webradio
AudioFileSourceICYStream *ifile = NULL;
AudioFileSourceBuffer *buff = NULL;
char wr_title[64];
void *preallocateBuffer = NULL;
void *preallocateCodec = NULL;
uint32_t retryms = 0;
TaskHandle_t mp3_task_handle;
TaskHandle_t mic_task_handle;
uint32_t mic_size;
uint8_t *mic_buff;
char mic_path[32];
File fwp;
uint8_t mic_stop;
int8_t mic_error;
bool use_stream = false;
// SHINE
uint32_t recdur;
uint8_t stream_active;
uint8_t stream_enable;
WiFiClient client;
ESP8266WebServer *MP3Server;
// I2S_BRIDGE
BRIDGE_MODE bridge_mode;
WiFiUDP i2s_bridge_udp;
WiFiUDP i2s_bridgec_udp;
IPAddress i2s_bridge_ip;
TaskHandle_t i2s_bridge_h;
int8_t ptt_pin = -1;
} audio_i2s;
extern FS *ufsp;
extern FS *ffsp;
const int preallocateBufferSize = 16*1024;
const int preallocateCodecSize = 29192; // MP3 codec max mem needed
//const int preallocateCodecSize = 85332; // AAC+SBR codec max mem needed
enum : int { EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2 };
void sayTime(int hour, int minutes);
void Cmndwav2mp3(void);
void Cmd_Time(void);
void Rtttl(char *buffer);
void CmndI2SRtttl(void);
/*********************************************************************************************\
* Class for outputting sound as endpoint for ESP8266Audio library
\*********************************************************************************************/
class TasmotaAudioOutputI2S : public AudioOutput
{
public:
// Constructor takes no parameter, everything is configured from template and config file
TasmotaAudioOutputI2S() {
hertz = 16000;
i2sOn = false;
bps = I2S_DATA_BIT_WIDTH_16BIT;
mono = audio_i2s.Settings->tx.mono;
lsbJustified = audio_i2s.Settings->tx.lsbJustified;
channels = mono ? I2S_SLOT_MODE_MONO : I2S_SLOT_MODE_STEREO;
output_mode = EXTERNAL_I2S;
tx_is_enabled = false;
}
~TasmotaAudioOutputI2S() {
if(i2sOn){
this->stop();
i2s_del_channel(tx_chan);
}
}
// ------------------------------------------------------------------------------------------
// Setters for configuration parameters
// ------------------------------------------------------------------------------------------
bool SetBitsPerSample(int bits) {
if ( (bits != 16) && (bits != 8) ) { return false; }
this->bps = bits;
return true;
}
bool SetChannels(int channels) {
if ((channels < 1) || (channels > 2)) { return false; }
if (channels == (int)this->channels) { return true; }
this->channels = channels;
return true;
}
bool SetRate(int hz) {
if (hz == (int)this->hertz) { return true; }
this->hertz = hz;
if(i2sOn){
int result = updateClockConfig();
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: SetRate: %i - %i"),hz, result);
}
return true;
}
bool SetPinout() { // TODO is this method still useful?
return this->startI2SChannel();
}
bool begin(void);
bool stop(void);
bool ConsumeSample(int16_t sample[2]);
bool startI2SChannel(void);
int updateClockConfig(void);
protected:
enum : int { EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2 };
int output_mode;
bool i2sOn;
bool mono;
bool lsbJustified;
i2s_chan_handle_t tx_chan;
bool tx_is_enabled;
};
// ------------------------------------------------------------------------------------------
// Methods
// ------------------------------------------------------------------------------------------
bool TasmotaAudioOutputI2S::begin() {
if (tx_is_enabled) { return true; }
if (!i2sOn) {
if (audio_i2s.Settings->sys.duplex == 0 && audio_i2s.Settings->sys.rx == 1) {
this->startI2SChannel();
}
}
int result = i2s_channel_enable(tx_chan);
if (result != 0){
AddLog(LOG_LEVEL_INFO, "I2S: Could not enable i2s_channel: %i", result);
return false;
}
tx_is_enabled = true;
return true;
}
bool TasmotaAudioOutputI2S::stop() {
i2s_channel_disable(tx_chan);
if(audio_i2s.Settings->sys.duplex == 0 && audio_i2s.Settings->sys.rx == 1){
i2s_del_channel(tx_chan);
i2sOn = false;
}
tx_is_enabled = false;
return true;
}
bool TasmotaAudioOutputI2S::ConsumeSample(int16_t sample[2]) {
if (!tx_is_enabled) { return false; }
int16_t ms[2];
ms[0] = sample[0];
ms[1] = sample[1];
MakeSampleStereo16(ms);
if (this->mono) {
// Average the two samples and overwrite
int32_t ttl = ms[LEFTCHANNEL] + ms[RIGHTCHANNEL];
ms[LEFTCHANNEL] = ms[RIGHTCHANNEL] = (ttl>>1) & 0xffff;
}
uint32_t s32;
if (output_mode == INTERNAL_DAC) {
int16_t l = Amplify(ms[LEFTCHANNEL]) + 0x8000;
int16_t r = Amplify(ms[RIGHTCHANNEL]) + 0x8000;
s32 = (r << 16) | (l & 0xffff);
} else {
s32 = ((Amplify(ms[RIGHTCHANNEL])) << 16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff);
}
size_t i2s_bytes_written;
i2s_channel_write(tx_chan, (const void*)&s32, sizeof(uint32_t), &i2s_bytes_written, 0);
return i2s_bytes_written;
}
bool TasmotaAudioOutputI2S::startI2SChannel(void) {
gpio_num_t _DIN = I2S_GPIO_UNUSED;
i2s_chan_config_t tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
if (audio_i2s.Settings->sys.duplex == 1) {
_DIN = (gpio_num_t)Pin(GPIO_I2S_DIN);
i2s_new_channel(&tx_chan_cfg, &tx_chan, &audio_i2s.rx_handle);
} else{
i2s_new_channel(&tx_chan_cfg, &tx_chan, NULL);
}
i2s_std_config_t tx_std_cfg = {
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(hertz),
.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG((i2s_data_bit_width_t)bps, (i2s_slot_mode_t)channels),
.gpio_cfg = {
.mclk = (gpio_num_t)Pin(GPIO_I2S_MCLK),
.bclk = (gpio_num_t)Pin(GPIO_I2S_BCLK),
.ws = (gpio_num_t)Pin(GPIO_I2S_WS),
.dout = (gpio_num_t)Pin(GPIO_I2S_DOUT),
.din = _DIN,
.invert_flags = {
.mclk_inv = false,
.bclk_inv = false,
.ws_inv = false,
},
},
};
i2sOn = (i2s_channel_init_std_mode(tx_chan, &tx_std_cfg) == 0);
AddLog(LOG_LEVEL_DEBUG, "I2S: TX channel with %i bit width on %i channels initialized", bps, channels);
if (audio_i2s.Settings->sys.duplex == 1) {
i2s_channel_init_std_mode(audio_i2s.rx_handle, &tx_std_cfg);
AddLog(LOG_LEVEL_DEBUG, "I2S: RX channel added in full duplex mode");
}
return i2sOn;
}
int TasmotaAudioOutputI2S::updateClockConfig(void) {
i2s_channel_disable(tx_chan);
i2s_std_clk_config_t clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(hertz);
#ifdef SOC_I2S_SUPPORTS_APLL
if(audio_i2s.Settings->tx.apll == 1){
clk_cfg.clk_src = I2S_CLK_SRC_APLL;
}
#endif
int result = i2s_channel_reconfig_std_clock(tx_chan, &clk_cfg );
if(tx_is_enabled) i2s_channel_enable(tx_chan);
return result;
}
#endif // USE_I2S_AUDIO
#endif //ESP_IDF_VERSION_MAJOR >= 5

View File

@ -22,327 +22,52 @@
#define XDRV_42 42
#include "driver/i2s_std.h"
#include "driver/i2s_pdm.h"
#include "driver/gpio.h"
#include "AudioFileSourcePROGMEM.h"
#include "AudioFileSourceID3.h"
#include "AudioGeneratorMP3.h"
#include <ESP8266SAM.h>
#include "AudioFileSourceFS.h"
#include "AudioGeneratorTalkie.h"
#include "AudioFileSourceICYStream.h"
#include "AudioFileSourceBuffer.h"
#include "AudioGeneratorAAC.h"
#include <layer3.h>
#include <types.h>
#undef AUDIO_PWR_ON
#undef AUDIO_PWR_OFF
#define AUDIO_PWR_ON I2SAudioPower(true);
#define AUDIO_PWR_OFF I2SAudioPower(false);
#define USE_I2S_SAY
#define USE_I2S_SAY_TIME
#define USE_I2S_RTTTL
/*********************************************************************************************\
* Driver Settings in memory
* Commands definitions
\*********************************************************************************************/
typedef struct{
struct{
uint32_t version : 8 = 0;
// runtime options, will be saved but ignored on setting read
uint32_t duplex : 1 = 0; // depends on GPIO setting and SOC caps, DIN and DOUT on same port in GPIO means -> try to use duplex if possible
uint32_t tx : 1 = 0; // depends on GPIO setting
uint32_t rx : 1 = 0; // depends on GPIO setting
} sys;
struct {
uint32_t mode : 2 = 0; // bit 0+1 STD = 0, PDM = 1, TDM = 2
uint32_t apll : 1 = 1; // bit 2 - will be ignored on unsupported SOC's
uint32_t mono : 1 = 0; // bit 3 0 = stereo, 1 = mono
uint32_t codec : 1 = 0; // bit 4 - S3 box only, unused for now
uint32_t webradio : 1 = 1; // bit 5 - allocate buffer for webradio
uint32_t spare01 : 1 = 1; // bit 6 - request duplex, means RX and TX on 1 slot
uint32_t lsbJustified : 1; // bit 7 - allow supporting LSBJ chips, e.g. TM8211/PT8211
uint32_t volume : 8 = 10; // bit 8-15
uint32_t spare02 : 16; // bit 16-31
} tx;
struct {
struct{
uint16_t sample_rate = 32000;
uint8_t gain = 30;
uint8_t mode = 0; //STD = 0, PDM = 1, TDM = 2
uint8_t slot_mask : 2 = 1; // left = 1 /right = 2 /both = 3
uint8_t slot_mode : 1 = 0; // mono/stereo - 1 is added for both
uint8_t codec : 1 = 0; // unused for now
uint8_t mp3_encoder : 1 = 1; // will be ignored without PS-RAM
};
} rx;
} tI2SSettings;
typedef union {
uint8_t data;
struct {
uint8_t master : 1;
uint8_t enabled : 1;
uint8_t swap_mic : 1;
uint8_t mode : 2;
};
} BRIDGE_MODE;
class TasmotaAudioOutputI2S;
struct AUDIO_I2S_t {
tI2SSettings *Settings;
i2s_chan_handle_t rx_handle = nullptr;
AudioGeneratorMP3 *mp3 = nullptr;
AudioFileSourceFS *file;
TasmotaAudioOutputI2S *out;
AudioFileSourceID3 *id3;
AudioGeneratorMP3 *decoder = NULL;
void *mp3ram = NULL;
// Webradio
AudioFileSourceICYStream *ifile = NULL;
AudioFileSourceBuffer *buff = NULL;
char wr_title[64];
void *preallocateBuffer = NULL;
void *preallocateCodec = NULL;
uint32_t retryms = 0;
TaskHandle_t mp3_task_handle;
TaskHandle_t mic_task_handle;
uint32_t mic_size;
uint8_t *mic_buff;
char mic_path[32];
File fwp;
uint8_t mic_stop;
int8_t mic_error;
bool use_stream = false;
// SHINE
uint32_t recdur;
uint8_t stream_active;
uint8_t stream_enable;
WiFiClient client;
ESP8266WebServer *MP3Server;
// I2S_BRIDGE
BRIDGE_MODE bridge_mode;
WiFiUDP i2s_bridge_udp;
WiFiUDP i2s_bridgec_udp;
IPAddress i2s_bridge_ip;
TaskHandle_t i2s_bridge_h;
int8_t ptt_pin = -1;
} audio_i2s;
extern FS *ufsp;
extern FS *ffsp;
const int preallocateBufferSize = 16*1024;
const int preallocateCodecSize = 29192; // MP3 codec max mem needed
//const int preallocateCodecSize = 85332; // AAC+SBR codec max mem needed
enum : int { EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2 };
void sayTime(int hour, int minutes);
void Cmndwav2mp3(void);
void Cmd_Time(void);
void Rtttl(char *buffer);
void CmndI2SRtttl(void);
/*********************************************************************************************\
* Class for outputting sound as endpoint for ESP8266Audio library
\*********************************************************************************************/
class TasmotaAudioOutputI2S : public AudioOutput
{
public:
TasmotaAudioOutputI2S(){
hertz = 16000;
i2sOn = false;
bps = I2S_DATA_BIT_WIDTH_16BIT;
mono = (audio_i2s.Settings->tx.mono == 1);
channels = mono ? I2S_SLOT_MODE_MONO : I2S_SLOT_MODE_STEREO;
output_mode = EXTERNAL_I2S;
tx_is_enabled = false;
}
~TasmotaAudioOutputI2S(){
if(i2sOn){
this->stop();
i2s_del_channel(tx_chan);
}
}
bool SetBitsPerSample(int bits)
{
if ( (bits != 16) && (bits != 8) ) return false;
this->bps = bits;
return true;
}
bool SetChannels(int channels)
{
if ( (channels < 1) || (channels > 2) ) return false;
if (channels == (int)this->channels) return true;
this->channels = channels;
return true;
}
bool SetRate(int hz){
if (hz == (int)this->hertz) return true;
this->hertz = hz;
if(i2sOn){
int result = updateClockConfig();
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: SetRate: %i - %i"),hz, result);
}
return true;
}
bool SetPinout(){
return this->startI2SChannel();
}
bool begin(){
if(tx_is_enabled) return true;
if(i2sOn == false){
if(audio_i2s.Settings->sys.duplex == 0 && audio_i2s.Settings->sys.rx == 1){
this->startI2SChannel();
}
}
int result = i2s_channel_enable(tx_chan);
if(result != 0){
AddLog(LOG_LEVEL_INFO,PSTR("I2S: Could not enable i2s_channel: %i"), result);
return false;
}
tx_is_enabled = true;
return true;
}
bool stop(){
i2s_channel_disable(tx_chan);
if(audio_i2s.Settings->sys.duplex == 0 && audio_i2s.Settings->sys.rx == 1){
i2s_del_channel(tx_chan);
i2sOn = false;
}
tx_is_enabled = false;
return true;
}
bool ConsumeSample(int16_t sample[2])
{
if (!tx_is_enabled)
return false;
int16_t ms[2];
ms[0] = sample[0];
ms[1] = sample[1];
MakeSampleStereo16( ms );
if (this->mono) {
// Average the two samples and overwrite
int32_t ttl = ms[LEFTCHANNEL] + ms[RIGHTCHANNEL];
ms[LEFTCHANNEL] = ms[RIGHTCHANNEL] = (ttl>>1) & 0xffff;
}
uint32_t s32;
if (output_mode == INTERNAL_DAC)
{
int16_t l = Amplify(ms[LEFTCHANNEL]) + 0x8000;
int16_t r = Amplify(ms[RIGHTCHANNEL]) + 0x8000;
s32 = (r << 16) | (l & 0xffff);
}
else
{
s32 = ((Amplify(ms[RIGHTCHANNEL])) << 16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff);
}
size_t i2s_bytes_written;
i2s_channel_write(tx_chan, (const void*)&s32, sizeof(uint32_t), &i2s_bytes_written, 0);
return i2s_bytes_written;
}
private:
enum : int { EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2 };
int output_mode;
bool i2sOn;
bool mono;
i2s_chan_handle_t tx_chan;
bool tx_is_enabled;
bool startI2SChannel(){
gpio_num_t _DIN = I2S_GPIO_UNUSED;
i2s_chan_config_t tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
if(audio_i2s.Settings->sys.duplex == 1){
_DIN = (gpio_num_t)Pin(GPIO_I2S_DIN);
i2s_new_channel(&tx_chan_cfg, &tx_chan, &audio_i2s.rx_handle);
}
else{
i2s_new_channel(&tx_chan_cfg, &tx_chan, NULL);
}
i2s_std_config_t tx_std_cfg = {
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(hertz),
.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG((i2s_data_bit_width_t)bps, (i2s_slot_mode_t)channels),
.gpio_cfg = {
.mclk = (gpio_num_t)Pin(GPIO_I2S_MCLK),
.bclk = (gpio_num_t)Pin(GPIO_I2S_BCLK),
.ws = (gpio_num_t)Pin(GPIO_I2S_WS),
.dout = (gpio_num_t)Pin(GPIO_I2S_DOUT),
.din = _DIN,
.invert_flags = {
.mclk_inv = false,
.bclk_inv = false,
.ws_inv = false,
},
},
};
i2sOn = (i2s_channel_init_std_mode(tx_chan, &tx_std_cfg) == 0);
AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: TX channel with %i bit width on %i channels initialized"),bps, channels);
if(audio_i2s.Settings->sys.duplex == 1){
i2s_channel_init_std_mode(audio_i2s.rx_handle, &tx_std_cfg);
AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: RX channel added in full duplex mode"));
}
return i2sOn;
}
int updateClockConfig(){
i2s_channel_disable(tx_chan);
i2s_std_clk_config_t clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(hertz);
#ifdef SOC_I2S_SUPPORTS_APLL
if(audio_i2s.Settings->tx.apll == 1){
clk_cfg.clk_src = I2S_CLK_SRC_APLL;
}
const char kI2SAudio_Commands[] PROGMEM = "I2S|"
"Gain|Play|WR|Rec|MGain|Stop"
#ifdef USE_I2S_SAY
"|Say"
#endif // USE_I2S_SAY
#ifdef USE_I2S_SAY_TIME
"|Time"
#endif // USE_I2S_SAY_TIME
#ifdef USE_I2S_RTTTL
"|Rtttl"
#endif
int result = i2s_channel_reconfig_std_clock(tx_chan, &clk_cfg );
if(tx_is_enabled) i2s_channel_enable(tx_chan);
return result;
}
#if defined(USE_SHINE) && defined(MP3_MIC_STREAM)
"|Stream"
#endif // MP3_MIC_STREAM
#ifdef I2S_BRIDGE
"|Bridge"
#endif // I2S_BRIDGE
;
void (* const I2SAudio_Command[])(void) PROGMEM = {
&CmndI2SGain, &CmndI2SPlay, &CmndI2SWebRadio, &CmndI2SMicRec, &CmndI2SMicGain, &CmndI2SStop,
#ifdef USE_I2S_SAY
&CmndI2SSay,
#endif // USE_I2S_SAY
#ifdef USE_I2S_SAY_TIME
&Cmd_Time,
#endif // USE_I2S_SAY_TIME
#ifdef USE_I2S_RTTTL
&CmndI2SI2SRtttl,
#endif
#if defined(USE_SHINE) && defined(MP3_MIC_STREAM)
&CmndI2SMP3Stream,
#endif // MP3_MIC_STREAM
#ifdef I2S_BRIDGE
&CmndI2SI2SBridge,
#endif // I2S_BRIDGE
};
/*********************************************************************************************\
* microphone related functions
\*********************************************************************************************/
uint32_t I2sMicInit(uint8_t enable) {
esp_err_t err = ESP_OK;
i2s_slot_mode_t slot_mode = (audio_i2s.Settings->rx.slot_mode == 0) ? I2S_SLOT_MODE_MONO : I2S_SLOT_MODE_STEREO;
@ -901,46 +626,6 @@ void Say(char *text) {
* Commands
\*********************************************************************************************/
const char kI2SAudio_Commands[] PROGMEM = "I2S|"
"Gain|Play|WR|Rec|MGain|Stop"
#ifdef USE_I2S_SAY
"|Say"
#ifdef USE_I2S_SAY_TIME
"|Time"
#endif // USE_I2S_SAY_TIME
#endif // USE_I2S_SAY
#ifdef USE_I2S_RTTTL
"|Rtttl"
#endif
#if defined(USE_SHINE) && defined(MP3_MIC_STREAM)
"|Stream"
#endif // MP3_MIC_STREAM
#ifdef I2S_BRIDGE
"|Bridge"
#endif // I2S_BRIDGE
;
void (* const I2SAudio_Command[])(void) PROGMEM = {
&CmndI2SGain, &CmndI2SPlay, &CmndI2SWebRadio, &CmndI2SMicRec, &CmndI2SMicGain, &CmndI2SStop,
#ifdef USE_I2S_SAY
&CmndI2SSay,
#ifdef USE_I2S_SAY_TIME
&Cmd_Time,
#endif // USE_I2S_SAY_TIME
#endif // USE_I2S_SAY
#ifdef USE_I2S_RTTTL
&CmndI2SI2SRtttl,
#endif
#if defined(USE_SHINE) && defined(MP3_MIC_STREAM)
&CmndI2SMP3Stream,
#endif // MP3_MIC_STREAM
#ifdef I2S_BRIDGE
&CmndI2SI2SBridge,
#endif // I2S_BRIDGE
};
void CmndI2SStop(void) {
I2sStopPlaying();
ResponseCmndDone();

View File

@ -22,15 +22,15 @@
#ifdef USE_I2S_AUDIO_BERRY
// #include "AudioFileSourceSPIFFS.h"
// #include "AudioFileSourceID3.h"
#include "AudioOutputI2S.h"
#include "AudioGeneratorWAV.h"
#include "AudioGeneratorMP3.h"
#include "AudioFileSourceFS.h"
#include <berry.h>
#if ESP_IDF_VERSION_MAJOR < 5
#error "USE_I2S_AUDIO_BERRY is only supported for ESP-IDF 5.1 or later"
#endif
/*********************************************************************************************\
* AudioOutput class
@ -38,44 +38,97 @@
\*********************************************************************************************/
extern "C" {
// AudioOutput.set_rate(rate_hz:int) -> bool
void* be_audio_output_init(void) {
return new AudioOutput();
}
// AudioOutput.set_rate(rate_hz:int) -> bool
int be_audio_output_set_rate(AudioOutput* out, int hz) {
return out->SetRate(hz);
}
// AudioOutput.set_bits_per_sample(bits_per_sample:int) -> bool
int be_audio_output_set_bits_per_sample(AudioOutput* out, int bps) {
return out->SetBitsPerSample(bps);
}
// AudioOutput.set_channels(channels:int) -> bool
int be_audio_output_set_channels(AudioOutput* out, int channels) {
return out->SetChannels(channels);
}
// AudioOutput.set_gain(gain:real) -> bool
int be_audio_output_set_gain(AudioOutput* out, float gain) {
return out->SetGain(gain);
}
// AudioOutput.begin() -> bool
int be_audio_output_begin(AudioOutput* out) {
return out->begin();
}
// AudioOutput.stop() -> bool
int be_audio_output_stop(AudioOutput* out) {
return out->stop();
}
// AudioOutput.flush() -> bool
void be_audio_output_flush(AudioOutput* out) {
out->flush();
}
// AudioOutput.consume_mono(bytes) -> int
int be_audio_output_consume_mono(AudioOutput* out, uint16_t *pcm, int bytes_len, int index) {
int pcm_len = bytes_len / 2;
int n;
// berry_log_C("be_audio_output_consume_mono_ntv out=%p pcm=%p bytes_len=%i index=%i", out, pcm, bytes_len, index);
for (n = 0; index + n < pcm_len; n++) {
int16_t ms[2];
ms[AudioOutput::LEFTCHANNEL] = ms[AudioOutput::RIGHTCHANNEL] = pcm[index + n];
if (!out->ConsumeSample(ms)) { break; }
}
return n;
}
// AudioOutput.consume_stereo(bytes) -> int
int be_audio_output_consume_stereo(AudioOutput* out, uint16_t *pcm, int bytes_len, int index) {
int pcm_len = bytes_len / 4; // 2 samples LEFT+RIGHT of 2 bytes each
int n;
// berry_log_C("be_audio_output_consume_stereo_ntv out=%p pcm=%p bytes_len=%i index=%i", out, pcm, bytes_len, index);
for (n = 0; index + n < pcm_len; n++) {
int16_t ms[2];
ms[AudioOutput::LEFTCHANNEL] = pcm[index + n + n];
ms[AudioOutput::RIGHTCHANNEL] = pcm[index + n + n + 1];
if (!out->ConsumeSample(ms)) { break; }
}
return n;
}
// AudioOutput.consume_silence() -> int, push silence frames
int be_audio_output_consume_silence(AudioOutput* out) {
int n = 0;
int16_t ms[2] = {0, 0};
while (true) {
if (!out->ConsumeSample(ms)) { break; }
n++;
}
return n;
}
//
// AudioOutputI2S(bclkPin: int, wclkPin: int, doutPin: int[, port:int, dmabuf:int, mode: int])
// AudioOutputI2S()
//
int i2s_output_i2s_init(bvm *vm) {
int argc = be_top(vm);
if (argc > 3) {
int bclkPin = be_toint(vm, 2);
int wclkPin = be_toint(vm, 3);
int doutPin = be_toint(vm, 4);
int port = 0;
if (argc > 4) {
port = be_toint(vm, 5);
}
int dma_buf_count = 8; // number of dma buffers of 64 bytes
if (argc > 5) {
dma_buf_count = be_toint(vm, 6);
}
int mode = 0; // EXTERNAL_I2S
if (argc > 6) {
mode = be_toint(vm, 7);
}
// AudioOutputI2S(int port=0, int output_mode=EXTERNAL_I2S, int dma_buf_count = 8, int use_apll=APLL_DISABLE);
AudioOutputI2S * audio = new AudioOutputI2S(port, mode, dma_buf_count);
if (0 == mode) {
audio->SetPinout(bclkPin, wclkPin, doutPin); // return value has no useful information for us
}
be_pushcomptr(vm, (void*) audio);
be_setmember(vm, 1, ".p");
be_return_nil(vm);
}
be_raise(vm, kTypeError, nullptr);
TasmotaAudioOutputI2S * audio = new TasmotaAudioOutputI2S();
be_pushcomptr(vm, (void*) audio);
be_setmember(vm, 1, ".p");
be_return_nil(vm);
}
int i2s_output_i2s_deinit(bvm *vm) {
int argc = be_top(vm);
be_getmember(vm, 1, ".p");
AudioOutputI2S * audio = (AudioOutputI2S *) be_tocomptr(vm, -1);
TasmotaAudioOutputI2S * audio = (TasmotaAudioOutputI2S *) be_tocomptr(vm, -1);
if (audio) {
delete audio;
// clear
@ -89,7 +142,7 @@ extern "C" {
int i2s_output_i2s_stop(bvm *vm) {
int argc = be_top(vm);
be_getmember(vm, 1, ".p");
AudioOutputI2S * audio = (AudioOutputI2S *) be_tocomptr(vm, -1);
TasmotaAudioOutputI2S * audio = (TasmotaAudioOutputI2S *) be_tocomptr(vm, -1);
if (audio) {
audio->stop();
}