mirror of
https://github.com/esphome/esphome.git
synced 2024-11-25 00:18:11 +01:00
[i2s_audio] Add more options to speakers and microphones (#7306)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
dbecade122
commit
04248b6840
18 changed files with 136 additions and 81 deletions
|
@ -30,6 +30,10 @@ CONF_I2S_MODE = "i2s_mode"
|
||||||
CONF_PRIMARY = "primary"
|
CONF_PRIMARY = "primary"
|
||||||
CONF_SECONDARY = "secondary"
|
CONF_SECONDARY = "secondary"
|
||||||
|
|
||||||
|
CONF_USE_APLL = "use_apll"
|
||||||
|
CONF_BITS_PER_SAMPLE = "bits_per_sample"
|
||||||
|
CONF_BITS_PER_CHANNEL = "bits_per_channel"
|
||||||
|
CONF_MONO = "mono"
|
||||||
CONF_LEFT = "left"
|
CONF_LEFT = "left"
|
||||||
CONF_RIGHT = "right"
|
CONF_RIGHT = "right"
|
||||||
CONF_STEREO = "stereo"
|
CONF_STEREO = "stereo"
|
||||||
|
@ -58,6 +62,7 @@ I2S_PORTS = {
|
||||||
|
|
||||||
i2s_channel_fmt_t = cg.global_ns.enum("i2s_channel_fmt_t")
|
i2s_channel_fmt_t = cg.global_ns.enum("i2s_channel_fmt_t")
|
||||||
I2S_CHANNELS = {
|
I2S_CHANNELS = {
|
||||||
|
CONF_MONO: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ALL_LEFT,
|
||||||
CONF_LEFT: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_LEFT,
|
CONF_LEFT: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_LEFT,
|
||||||
CONF_RIGHT: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_RIGHT,
|
CONF_RIGHT: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_RIGHT,
|
||||||
CONF_STEREO: i2s_channel_fmt_t.I2S_CHANNEL_FMT_RIGHT_LEFT,
|
CONF_STEREO: i2s_channel_fmt_t.I2S_CHANNEL_FMT_RIGHT_LEFT,
|
||||||
|
@ -67,17 +72,25 @@ i2s_bits_per_sample_t = cg.global_ns.enum("i2s_bits_per_sample_t")
|
||||||
I2S_BITS_PER_SAMPLE = {
|
I2S_BITS_PER_SAMPLE = {
|
||||||
8: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_8BIT,
|
8: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_8BIT,
|
||||||
16: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_16BIT,
|
16: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_16BIT,
|
||||||
|
24: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_24BIT,
|
||||||
32: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_32BIT,
|
32: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_32BIT,
|
||||||
}
|
}
|
||||||
|
|
||||||
INTERNAL_ADC_VARIANTS = [VARIANT_ESP32]
|
i2s_bits_per_chan_t = cg.global_ns.enum("i2s_bits_per_chan_t")
|
||||||
PDM_VARIANTS = [VARIANT_ESP32, VARIANT_ESP32S3]
|
I2S_BITS_PER_CHANNEL = {
|
||||||
|
"default": i2s_bits_per_chan_t.I2S_BITS_PER_CHAN_DEFAULT,
|
||||||
|
8: i2s_bits_per_chan_t.I2S_BITS_PER_CHAN_8BIT,
|
||||||
|
16: i2s_bits_per_chan_t.I2S_BITS_PER_CHAN_16BIT,
|
||||||
|
24: i2s_bits_per_chan_t.I2S_BITS_PER_CHAN_24BIT,
|
||||||
|
32: i2s_bits_per_chan_t.I2S_BITS_PER_CHAN_32BIT,
|
||||||
|
}
|
||||||
|
|
||||||
_validate_bits = cv.float_with_unit("bits", "bit")
|
_validate_bits = cv.float_with_unit("bits", "bit")
|
||||||
|
|
||||||
|
|
||||||
def i2s_audio_component_schema(
|
def i2s_audio_component_schema(
|
||||||
class_: MockObjClass,
|
class_: MockObjClass,
|
||||||
|
*,
|
||||||
default_sample_rate: int,
|
default_sample_rate: int,
|
||||||
default_channel: str,
|
default_channel: str,
|
||||||
default_bits_per_sample: str,
|
default_bits_per_sample: str,
|
||||||
|
@ -96,6 +109,11 @@ def i2s_audio_component_schema(
|
||||||
cv.Optional(CONF_I2S_MODE, default=CONF_PRIMARY): cv.enum(
|
cv.Optional(CONF_I2S_MODE, default=CONF_PRIMARY): cv.enum(
|
||||||
I2S_MODE_OPTIONS, lower=True
|
I2S_MODE_OPTIONS, lower=True
|
||||||
),
|
),
|
||||||
|
cv.Optional(CONF_USE_APLL, default=False): cv.boolean,
|
||||||
|
cv.Optional(CONF_BITS_PER_CHANNEL, default="default"): cv.All(
|
||||||
|
cv.Any(cv.float_with_unit("bits", "bit"), "default"),
|
||||||
|
cv.enum(I2S_BITS_PER_CHANNEL),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -107,6 +125,8 @@ async def register_i2s_audio_component(var, config):
|
||||||
cg.add(var.set_channel(config[CONF_CHANNEL]))
|
cg.add(var.set_channel(config[CONF_CHANNEL]))
|
||||||
cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE]))
|
cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE]))
|
||||||
cg.add(var.set_bits_per_sample(config[CONF_BITS_PER_SAMPLE]))
|
cg.add(var.set_bits_per_sample(config[CONF_BITS_PER_SAMPLE]))
|
||||||
|
cg.add(var.set_bits_per_channel(config[CONF_BITS_PER_CHANNEL]))
|
||||||
|
cg.add(var.set_use_apll(config[CONF_USE_APLL]))
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema(
|
CONFIG_SCHEMA = cv.Schema(
|
||||||
|
|
|
@ -17,12 +17,16 @@ class I2SAudioBase : public Parented<I2SAudioComponent> {
|
||||||
void set_channel(i2s_channel_fmt_t channel) { this->channel_ = channel; }
|
void set_channel(i2s_channel_fmt_t channel) { this->channel_ = channel; }
|
||||||
void set_sample_rate(uint32_t sample_rate) { this->sample_rate_ = sample_rate; }
|
void set_sample_rate(uint32_t sample_rate) { this->sample_rate_ = sample_rate; }
|
||||||
void set_bits_per_sample(i2s_bits_per_sample_t bits_per_sample) { this->bits_per_sample_ = bits_per_sample; }
|
void set_bits_per_sample(i2s_bits_per_sample_t bits_per_sample) { this->bits_per_sample_ = bits_per_sample; }
|
||||||
|
void set_bits_per_channel(i2s_bits_per_chan_t bits_per_channel) { this->bits_per_channel_ = bits_per_channel; }
|
||||||
|
void set_use_apll(uint32_t use_apll) { this->use_apll_ = use_apll; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
i2s_mode_t i2s_mode_{};
|
i2s_mode_t i2s_mode_{};
|
||||||
i2s_channel_fmt_t channel_;
|
i2s_channel_fmt_t channel_;
|
||||||
uint32_t sample_rate_;
|
uint32_t sample_rate_;
|
||||||
i2s_bits_per_sample_t bits_per_sample_;
|
i2s_bits_per_sample_t bits_per_sample_;
|
||||||
|
i2s_bits_per_chan_t bits_per_channel_;
|
||||||
|
bool use_apll_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class I2SAudioIn : public I2SAudioBase {};
|
class I2SAudioIn : public I2SAudioBase {};
|
||||||
|
|
|
@ -12,6 +12,10 @@ from .. import (
|
||||||
I2SAudioOut,
|
I2SAudioOut,
|
||||||
CONF_I2S_AUDIO_ID,
|
CONF_I2S_AUDIO_ID,
|
||||||
CONF_I2S_DOUT_PIN,
|
CONF_I2S_DOUT_PIN,
|
||||||
|
CONF_LEFT,
|
||||||
|
CONF_RIGHT,
|
||||||
|
CONF_MONO,
|
||||||
|
CONF_STEREO,
|
||||||
)
|
)
|
||||||
|
|
||||||
CODEOWNERS = ["@jesserockz"]
|
CODEOWNERS = ["@jesserockz"]
|
||||||
|
@ -30,12 +34,12 @@ CONF_DAC_TYPE = "dac_type"
|
||||||
CONF_I2S_COMM_FMT = "i2s_comm_fmt"
|
CONF_I2S_COMM_FMT = "i2s_comm_fmt"
|
||||||
|
|
||||||
INTERNAL_DAC_OPTIONS = {
|
INTERNAL_DAC_OPTIONS = {
|
||||||
"left": i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN,
|
CONF_LEFT: i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN,
|
||||||
"right": i2s_dac_mode_t.I2S_DAC_CHANNEL_RIGHT_EN,
|
CONF_RIGHT: i2s_dac_mode_t.I2S_DAC_CHANNEL_RIGHT_EN,
|
||||||
"stereo": i2s_dac_mode_t.I2S_DAC_CHANNEL_BOTH_EN,
|
CONF_STEREO: i2s_dac_mode_t.I2S_DAC_CHANNEL_BOTH_EN,
|
||||||
}
|
}
|
||||||
|
|
||||||
EXTERNAL_DAC_OPTIONS = ["mono", "stereo"]
|
EXTERNAL_DAC_OPTIONS = [CONF_MONO, CONF_STEREO]
|
||||||
|
|
||||||
NO_INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32S2]
|
NO_INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32S2]
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ enum I2SState : uint8_t {
|
||||||
I2S_STATE_STOPPING,
|
I2S_STATE_STOPPING,
|
||||||
};
|
};
|
||||||
|
|
||||||
class I2SAudioMediaPlayer : public Component, public media_player::MediaPlayer, public I2SAudioOut {
|
class I2SAudioMediaPlayer : public Component, public Parented<I2SAudioComponent>, public media_player::MediaPlayer {
|
||||||
public:
|
public:
|
||||||
void setup() override;
|
void setup() override;
|
||||||
float get_setup_priority() const override { return esphome::setup_priority::LATE; }
|
float get_setup_priority() const override { return esphome::setup_priority::LATE; }
|
||||||
|
|
|
@ -8,8 +8,6 @@ from esphome.const import CONF_ID, CONF_NUMBER
|
||||||
from .. import (
|
from .. import (
|
||||||
CONF_I2S_DIN_PIN,
|
CONF_I2S_DIN_PIN,
|
||||||
CONF_RIGHT,
|
CONF_RIGHT,
|
||||||
INTERNAL_ADC_VARIANTS,
|
|
||||||
PDM_VARIANTS,
|
|
||||||
I2SAudioIn,
|
I2SAudioIn,
|
||||||
i2s_audio_component_schema,
|
i2s_audio_component_schema,
|
||||||
i2s_audio_ns,
|
i2s_audio_ns,
|
||||||
|
@ -23,12 +21,13 @@ CONF_ADC_PIN = "adc_pin"
|
||||||
CONF_ADC_TYPE = "adc_type"
|
CONF_ADC_TYPE = "adc_type"
|
||||||
CONF_PDM = "pdm"
|
CONF_PDM = "pdm"
|
||||||
|
|
||||||
CONF_USE_APLL = "use_apll"
|
|
||||||
|
|
||||||
I2SAudioMicrophone = i2s_audio_ns.class_(
|
I2SAudioMicrophone = i2s_audio_ns.class_(
|
||||||
"I2SAudioMicrophone", I2SAudioIn, microphone.Microphone, cg.Component
|
"I2SAudioMicrophone", I2SAudioIn, microphone.Microphone, cg.Component
|
||||||
)
|
)
|
||||||
|
|
||||||
|
INTERNAL_ADC_VARIANTS = [esp32.const.VARIANT_ESP32]
|
||||||
|
PDM_VARIANTS = [esp32.const.VARIANT_ESP32, esp32.const.VARIANT_ESP32S3]
|
||||||
|
|
||||||
|
|
||||||
def validate_esp32_variant(config):
|
def validate_esp32_variant(config):
|
||||||
variant = esp32.get_esp32_variant()
|
variant = esp32.get_esp32_variant()
|
||||||
|
@ -45,9 +44,15 @@ def validate_esp32_variant(config):
|
||||||
|
|
||||||
|
|
||||||
BASE_SCHEMA = microphone.MICROPHONE_SCHEMA.extend(
|
BASE_SCHEMA = microphone.MICROPHONE_SCHEMA.extend(
|
||||||
i2s_audio_component_schema(I2SAudioMicrophone, 16000, CONF_RIGHT, "32bit")
|
i2s_audio_component_schema(
|
||||||
|
I2SAudioMicrophone,
|
||||||
|
default_sample_rate=16000,
|
||||||
|
default_channel=CONF_RIGHT,
|
||||||
|
default_bits_per_sample="32bit",
|
||||||
|
)
|
||||||
).extend(cv.COMPONENT_SCHEMA)
|
).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.typed_schema(
|
cv.typed_schema(
|
||||||
{
|
{
|
||||||
|
@ -59,8 +64,7 @@ CONFIG_SCHEMA = cv.All(
|
||||||
"external": BASE_SCHEMA.extend(
|
"external": BASE_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_I2S_DIN_PIN): pins.internal_gpio_input_pin_number,
|
cv.Required(CONF_I2S_DIN_PIN): pins.internal_gpio_input_pin_number,
|
||||||
cv.Required(CONF_PDM): cv.boolean,
|
cv.Optional(CONF_PDM, default=False): cv.boolean,
|
||||||
cv.Optional(CONF_USE_APLL, default=False): cv.boolean,
|
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -84,4 +88,3 @@ async def to_code(config):
|
||||||
else:
|
else:
|
||||||
cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN]))
|
cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN]))
|
||||||
cg.add(var.set_pdm(config[CONF_PDM]))
|
cg.add(var.set_pdm(config[CONF_PDM]))
|
||||||
cg.add(var.set_use_apll(config[CONF_USE_APLL]))
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ void I2SAudioMicrophone::start_() {
|
||||||
.tx_desc_auto_clear = false,
|
.tx_desc_auto_clear = false,
|
||||||
.fixed_mclk = 0,
|
.fixed_mclk = 0,
|
||||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||||
.bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT,
|
.bits_per_chan = this->bits_per_channel_,
|
||||||
};
|
};
|
||||||
|
|
||||||
esp_err_t err;
|
esp_err_t err;
|
||||||
|
@ -167,21 +167,24 @@ size_t I2SAudioMicrophone::read(int16_t *buf, size_t len) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
this->status_clear_warning();
|
this->status_clear_warning();
|
||||||
if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_16BIT) {
|
// ESP-IDF I2S implementation right-extends 8-bit data to 16 bits,
|
||||||
return bytes_read;
|
// and 24-bit data to 32 bits.
|
||||||
} else if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_32BIT) {
|
switch (this->bits_per_sample_) {
|
||||||
std::vector<int16_t> samples;
|
case I2S_BITS_PER_SAMPLE_8BIT:
|
||||||
size_t samples_read = bytes_read / sizeof(int32_t);
|
case I2S_BITS_PER_SAMPLE_16BIT:
|
||||||
samples.resize(samples_read);
|
return bytes_read;
|
||||||
for (size_t i = 0; i < samples_read; i++) {
|
case I2S_BITS_PER_SAMPLE_24BIT:
|
||||||
int32_t temp = reinterpret_cast<int32_t *>(buf)[i] >> 14;
|
case I2S_BITS_PER_SAMPLE_32BIT: {
|
||||||
samples[i] = clamp<int16_t>(temp, INT16_MIN, INT16_MAX);
|
size_t samples_read = bytes_read / sizeof(int32_t);
|
||||||
|
for (size_t i = 0; i < samples_read; i++) {
|
||||||
|
int32_t temp = reinterpret_cast<int32_t *>(buf)[i] >> 14;
|
||||||
|
buf[i] = clamp<int16_t>(temp, INT16_MIN, INT16_MAX);
|
||||||
|
}
|
||||||
|
return samples_read * sizeof(int16_t);
|
||||||
}
|
}
|
||||||
memcpy(buf, samples.data(), samples_read * sizeof(int16_t));
|
default:
|
||||||
return samples_read * sizeof(int16_t);
|
ESP_LOGE(TAG, "Unsupported bits per sample: %d", this->bits_per_sample_);
|
||||||
} else {
|
return 0;
|
||||||
ESP_LOGE(TAG, "Unsupported bits per sample: %d", this->bits_per_sample_);
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,8 +30,6 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void set_use_apll(uint32_t use_apll) { this->use_apll_ = use_apll; }
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void start_();
|
void start_();
|
||||||
void stop_();
|
void stop_();
|
||||||
|
@ -44,8 +42,6 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
|
||||||
#endif
|
#endif
|
||||||
bool pdm_{false};
|
bool pdm_{false};
|
||||||
|
|
||||||
bool use_apll_;
|
|
||||||
|
|
||||||
HighFrequencyLoopRequester high_freq_;
|
HighFrequencyLoopRequester high_freq_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,12 @@ from esphome import pins
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import esp32, speaker
|
from esphome.components import esp32, speaker
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_CHANNEL, CONF_ID
|
from esphome.const import CONF_CHANNEL, CONF_ID, CONF_MODE, CONF_TIMEOUT
|
||||||
|
|
||||||
from .. import (
|
from .. import (
|
||||||
CONF_I2S_DOUT_PIN,
|
CONF_I2S_DOUT_PIN,
|
||||||
CONF_LEFT,
|
CONF_LEFT,
|
||||||
|
CONF_MONO,
|
||||||
CONF_RIGHT,
|
CONF_RIGHT,
|
||||||
CONF_STEREO,
|
CONF_STEREO,
|
||||||
I2SAudioOut,
|
I2SAudioOut,
|
||||||
|
@ -32,7 +33,6 @@ INTERNAL_DAC_OPTIONS = {
|
||||||
CONF_STEREO: i2s_dac_mode_t.I2S_DAC_CHANNEL_BOTH_EN,
|
CONF_STEREO: i2s_dac_mode_t.I2S_DAC_CHANNEL_BOTH_EN,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
NO_INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32S2]
|
NO_INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32S2]
|
||||||
|
|
||||||
|
|
||||||
|
@ -45,14 +45,33 @@ def validate_esp32_variant(config):
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
BASE_SCHEMA = speaker.SPEAKER_SCHEMA.extend(
|
BASE_SCHEMA = (
|
||||||
i2s_audio_component_schema(I2SAudioSpeaker, 16000, "stereo", "16bit")
|
speaker.SPEAKER_SCHEMA.extend(
|
||||||
).extend(cv.COMPONENT_SCHEMA)
|
i2s_audio_component_schema(
|
||||||
|
I2SAudioSpeaker,
|
||||||
|
default_sample_rate=16000,
|
||||||
|
default_channel=CONF_MONO,
|
||||||
|
default_bits_per_sample="16bit",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.extend(
|
||||||
|
{
|
||||||
|
cv.Optional(
|
||||||
|
CONF_TIMEOUT, default="100ms"
|
||||||
|
): cv.positive_time_period_milliseconds,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.typed_schema(
|
cv.typed_schema(
|
||||||
{
|
{
|
||||||
"internal": BASE_SCHEMA,
|
"internal": BASE_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_MODE): cv.enum(INTERNAL_DAC_OPTIONS, lower=True),
|
||||||
|
}
|
||||||
|
),
|
||||||
"external": BASE_SCHEMA.extend(
|
"external": BASE_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
cv.Required(
|
cv.Required(
|
||||||
|
@ -77,3 +96,4 @@ async def to_code(config):
|
||||||
cg.add(var.set_internal_dac_mode(config[CONF_CHANNEL]))
|
cg.add(var.set_internal_dac_mode(config[CONF_CHANNEL]))
|
||||||
else:
|
else:
|
||||||
cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN]))
|
cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN]))
|
||||||
|
cg.add(var.set_timeout(config[CONF_TIMEOUT]))
|
||||||
|
|
|
@ -56,6 +56,21 @@ void I2SAudioSpeaker::start_() {
|
||||||
this->task_created_ = true;
|
this->task_created_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename a, typename b> const uint8_t *convert_data_format(const a *from, b *to, size_t &bytes, bool repeat) {
|
||||||
|
if (sizeof(a) == sizeof(b) && !repeat) {
|
||||||
|
return reinterpret_cast<const uint8_t *>(from);
|
||||||
|
}
|
||||||
|
const b *result = to;
|
||||||
|
for (size_t i = 0; i < bytes; i += sizeof(a)) {
|
||||||
|
b value = static_cast<b>(*from++) << (sizeof(b) - sizeof(a)) * 8;
|
||||||
|
*to++ = value;
|
||||||
|
if (repeat)
|
||||||
|
*to++ = value;
|
||||||
|
}
|
||||||
|
bytes *= (sizeof(b) / sizeof(a)) * (repeat ? 2 : 1); // NOLINT
|
||||||
|
return reinterpret_cast<const uint8_t *>(result);
|
||||||
|
}
|
||||||
|
|
||||||
void I2SAudioSpeaker::player_task(void *params) {
|
void I2SAudioSpeaker::player_task(void *params) {
|
||||||
I2SAudioSpeaker *this_speaker = (I2SAudioSpeaker *) params;
|
I2SAudioSpeaker *this_speaker = (I2SAudioSpeaker *) params;
|
||||||
|
|
||||||
|
@ -71,12 +86,12 @@ void I2SAudioSpeaker::player_task(void *params) {
|
||||||
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
|
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
|
||||||
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
|
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
|
||||||
.dma_buf_count = 8,
|
.dma_buf_count = 8,
|
||||||
.dma_buf_len = 128,
|
.dma_buf_len = 256,
|
||||||
.use_apll = false,
|
.use_apll = this_speaker->use_apll_,
|
||||||
.tx_desc_auto_clear = true,
|
.tx_desc_auto_clear = true,
|
||||||
.fixed_mclk = 0,
|
.fixed_mclk = 0,
|
||||||
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||||
.bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT,
|
.bits_per_chan = this_speaker->bits_per_channel_,
|
||||||
};
|
};
|
||||||
#if SOC_I2S_SUPPORTS_DAC
|
#if SOC_I2S_SUPPORTS_DAC
|
||||||
if (this_speaker->internal_dac_mode_ != I2S_DAC_CHANNEL_DISABLE) {
|
if (this_speaker->internal_dac_mode_ != I2S_DAC_CHANNEL_DISABLE) {
|
||||||
|
@ -114,10 +129,11 @@ void I2SAudioSpeaker::player_task(void *params) {
|
||||||
event.type = TaskEventType::STARTED;
|
event.type = TaskEventType::STARTED;
|
||||||
xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
|
xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
|
||||||
|
|
||||||
int16_t buffer[BUFFER_SIZE / 2];
|
int32_t buffer[BUFFER_SIZE];
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (xQueueReceive(this_speaker->buffer_queue_, &data_event, 100 / portTICK_PERIOD_MS) != pdTRUE) {
|
if (xQueueReceive(this_speaker->buffer_queue_, &data_event, this_speaker->timeout_ / portTICK_PERIOD_MS) !=
|
||||||
|
pdTRUE) {
|
||||||
break; // End of audio from main thread
|
break; // End of audio from main thread
|
||||||
}
|
}
|
||||||
if (data_event.stop) {
|
if (data_event.stop) {
|
||||||
|
@ -125,17 +141,28 @@ void I2SAudioSpeaker::player_task(void *params) {
|
||||||
xQueueReset(this_speaker->buffer_queue_); // Flush queue
|
xQueueReset(this_speaker->buffer_queue_); // Flush queue
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
size_t bytes_written;
|
|
||||||
|
|
||||||
memmove(buffer, data_event.data, data_event.len);
|
const uint8_t *data = data_event.data;
|
||||||
size_t remaining = data_event.len / 2;
|
size_t remaining = data_event.len;
|
||||||
size_t current = 0;
|
switch (this_speaker->bits_per_sample_) {
|
||||||
|
case I2S_BITS_PER_SAMPLE_8BIT:
|
||||||
|
case I2S_BITS_PER_SAMPLE_16BIT: {
|
||||||
|
data = convert_data_format(reinterpret_cast<const int16_t *>(data), reinterpret_cast<int16_t *>(buffer),
|
||||||
|
remaining, this_speaker->channel_ == I2S_CHANNEL_FMT_ALL_LEFT);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case I2S_BITS_PER_SAMPLE_24BIT:
|
||||||
|
case I2S_BITS_PER_SAMPLE_32BIT: {
|
||||||
|
data = convert_data_format(reinterpret_cast<const int16_t *>(data), reinterpret_cast<int32_t *>(buffer),
|
||||||
|
remaining, this_speaker->channel_ == I2S_CHANNEL_FMT_ALL_LEFT);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
while (remaining > 0) {
|
while (remaining != 0) {
|
||||||
uint32_t sample = (buffer[current] << 16) | (buffer[current] & 0xFFFF);
|
size_t bytes_written;
|
||||||
|
esp_err_t err =
|
||||||
esp_err_t err = i2s_write(this_speaker->parent_->get_port(), &sample, sizeof(sample), &bytes_written,
|
i2s_write(this_speaker->parent_->get_port(), data, remaining, &bytes_written, (32 / portTICK_PERIOD_MS));
|
||||||
(10 / portTICK_PERIOD_MS));
|
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
event = {.type = TaskEventType::WARNING, .err = err};
|
event = {.type = TaskEventType::WARNING, .err = err};
|
||||||
if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
|
if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
|
||||||
|
@ -143,21 +170,8 @@ void I2SAudioSpeaker::player_task(void *params) {
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (bytes_written != sizeof(sample)) {
|
data += bytes_written;
|
||||||
event = {.type = TaskEventType::WARNING, .err = ESP_FAIL};
|
remaining -= bytes_written;
|
||||||
if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
|
|
||||||
ESP_LOGW(TAG, "Failed to send WARNING event");
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
remaining--;
|
|
||||||
current++;
|
|
||||||
}
|
|
||||||
|
|
||||||
event.type = TaskEventType::PLAYING;
|
|
||||||
event.err = current;
|
|
||||||
if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
|
|
||||||
ESP_LOGW(TAG, "Failed to send PLAYING event");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,13 +227,11 @@ void I2SAudioSpeaker::watch_() {
|
||||||
case TaskEventType::STARTED:
|
case TaskEventType::STARTED:
|
||||||
ESP_LOGD(TAG, "Started I2S Audio Speaker");
|
ESP_LOGD(TAG, "Started I2S Audio Speaker");
|
||||||
this->state_ = speaker::STATE_RUNNING;
|
this->state_ = speaker::STATE_RUNNING;
|
||||||
|
this->status_clear_warning();
|
||||||
break;
|
break;
|
||||||
case TaskEventType::STOPPING:
|
case TaskEventType::STOPPING:
|
||||||
ESP_LOGD(TAG, "Stopping I2S Audio Speaker");
|
ESP_LOGD(TAG, "Stopping I2S Audio Speaker");
|
||||||
break;
|
break;
|
||||||
case TaskEventType::PLAYING:
|
|
||||||
this->status_clear_warning();
|
|
||||||
break;
|
|
||||||
case TaskEventType::STOPPED:
|
case TaskEventType::STOPPED:
|
||||||
this->state_ = speaker::STATE_STOPPED;
|
this->state_ = speaker::STATE_STOPPED;
|
||||||
vTaskDelete(this->player_task_handle_);
|
vTaskDelete(this->player_task_handle_);
|
||||||
|
|
|
@ -21,7 +21,6 @@ static const size_t BUFFER_SIZE = 1024;
|
||||||
enum class TaskEventType : uint8_t {
|
enum class TaskEventType : uint8_t {
|
||||||
STARTING = 0,
|
STARTING = 0,
|
||||||
STARTED,
|
STARTED,
|
||||||
PLAYING,
|
|
||||||
STOPPING,
|
STOPPING,
|
||||||
STOPPED,
|
STOPPED,
|
||||||
WARNING = 255,
|
WARNING = 255,
|
||||||
|
@ -45,6 +44,7 @@ class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Comp
|
||||||
void setup() override;
|
void setup() override;
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
|
||||||
|
void set_timeout(uint32_t ms) { this->timeout_ = ms; }
|
||||||
void set_dout_pin(uint8_t pin) { this->dout_pin_ = pin; }
|
void set_dout_pin(uint8_t pin) { this->dout_pin_ = pin; }
|
||||||
#if SOC_I2S_SUPPORTS_DAC
|
#if SOC_I2S_SUPPORTS_DAC
|
||||||
void set_internal_dac_mode(i2s_dac_mode_t mode) { this->internal_dac_mode_ = mode; }
|
void set_internal_dac_mode(i2s_dac_mode_t mode) { this->internal_dac_mode_ = mode; }
|
||||||
|
@ -69,6 +69,7 @@ class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Comp
|
||||||
QueueHandle_t buffer_queue_;
|
QueueHandle_t buffer_queue_;
|
||||||
QueueHandle_t event_queue_;
|
QueueHandle_t event_queue_;
|
||||||
|
|
||||||
|
uint32_t timeout_{0};
|
||||||
uint8_t dout_pin_{0};
|
uint8_t dout_pin_{0};
|
||||||
bool task_created_{false};
|
bool task_created_{false};
|
||||||
|
|
||||||
|
|
|
@ -21,4 +21,3 @@ speaker:
|
||||||
id: speaker_id
|
id: speaker_id
|
||||||
dac_type: external
|
dac_type: external
|
||||||
i2s_dout_pin: 13
|
i2s_dout_pin: 13
|
||||||
mode: mono
|
|
||||||
|
|
|
@ -21,4 +21,3 @@ speaker:
|
||||||
id: speaker_id
|
id: speaker_id
|
||||||
dac_type: external
|
dac_type: external
|
||||||
i2s_dout_pin: 3
|
i2s_dout_pin: 3
|
||||||
mode: mono
|
|
||||||
|
|
|
@ -21,4 +21,3 @@ speaker:
|
||||||
id: speaker_id
|
id: speaker_id
|
||||||
dac_type: external
|
dac_type: external
|
||||||
i2s_dout_pin: 3
|
i2s_dout_pin: 3
|
||||||
mode: mono
|
|
||||||
|
|
|
@ -21,4 +21,3 @@ speaker:
|
||||||
id: speaker_id
|
id: speaker_id
|
||||||
dac_type: external
|
dac_type: external
|
||||||
i2s_dout_pin: 13
|
i2s_dout_pin: 13
|
||||||
mode: mono
|
|
||||||
|
|
|
@ -28,7 +28,6 @@ speaker:
|
||||||
id: speaker_id
|
id: speaker_id
|
||||||
dac_type: external
|
dac_type: external
|
||||||
i2s_dout_pin: 12
|
i2s_dout_pin: 12
|
||||||
mode: mono
|
|
||||||
|
|
||||||
voice_assistant:
|
voice_assistant:
|
||||||
microphone: mic_id_external
|
microphone: mic_id_external
|
||||||
|
|
|
@ -28,7 +28,6 @@ speaker:
|
||||||
id: speaker_id
|
id: speaker_id
|
||||||
dac_type: external
|
dac_type: external
|
||||||
i2s_dout_pin: 2
|
i2s_dout_pin: 2
|
||||||
mode: mono
|
|
||||||
|
|
||||||
voice_assistant:
|
voice_assistant:
|
||||||
microphone: mic_id_external
|
microphone: mic_id_external
|
||||||
|
|
|
@ -28,7 +28,6 @@ speaker:
|
||||||
id: speaker_id
|
id: speaker_id
|
||||||
dac_type: external
|
dac_type: external
|
||||||
i2s_dout_pin: 2
|
i2s_dout_pin: 2
|
||||||
mode: mono
|
|
||||||
|
|
||||||
voice_assistant:
|
voice_assistant:
|
||||||
microphone: mic_id_external
|
microphone: mic_id_external
|
||||||
|
|
|
@ -28,7 +28,6 @@ speaker:
|
||||||
id: speaker_id
|
id: speaker_id
|
||||||
dac_type: external
|
dac_type: external
|
||||||
i2s_dout_pin: 12
|
i2s_dout_pin: 12
|
||||||
mode: mono
|
|
||||||
|
|
||||||
voice_assistant:
|
voice_assistant:
|
||||||
microphone: mic_id_external
|
microphone: mic_id_external
|
||||||
|
|
Loading…
Reference in a new issue