mirror of https://github.com/arendst/Tasmota.git
Minor refactoring of audio code for Arduino3 (#19559)
This commit is contained in:
parent
fc513af351
commit
7de25acac0
|
@ -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) {
|
|
@ -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
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue