mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00: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:
		| @@ -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,19 +167,22 @@ 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, | ||||||
|  |   // and 24-bit data to 32 bits. | ||||||
|  |   switch (this->bits_per_sample_) { | ||||||
|  |     case I2S_BITS_PER_SAMPLE_8BIT: | ||||||
|  |     case I2S_BITS_PER_SAMPLE_16BIT: | ||||||
|       return bytes_read; |       return bytes_read; | ||||||
|   } else if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_32BIT) { |     case I2S_BITS_PER_SAMPLE_24BIT: | ||||||
|     std::vector<int16_t> samples; |     case I2S_BITS_PER_SAMPLE_32BIT: { | ||||||
|       size_t samples_read = bytes_read / sizeof(int32_t); |       size_t samples_read = bytes_read / sizeof(int32_t); | ||||||
|     samples.resize(samples_read); |  | ||||||
|       for (size_t i = 0; i < samples_read; i++) { |       for (size_t i = 0; i < samples_read; i++) { | ||||||
|         int32_t temp = reinterpret_cast<int32_t *>(buf)[i] >> 14; |         int32_t temp = reinterpret_cast<int32_t *>(buf)[i] >> 14; | ||||||
|       samples[i] = clamp<int16_t>(temp, INT16_MIN, INT16_MAX); |         buf[i] = clamp<int16_t>(temp, INT16_MIN, INT16_MAX); | ||||||
|       } |       } | ||||||
|     memcpy(buf, samples.data(), samples_read * sizeof(int16_t)); |  | ||||||
|       return samples_read * sizeof(int16_t); |       return samples_read * sizeof(int16_t); | ||||||
|   } else { |     } | ||||||
|  |     default: | ||||||
|       ESP_LOGE(TAG, "Unsupported bits per sample: %d", this->bits_per_sample_); |       ESP_LOGE(TAG, "Unsupported bits per sample: %d", this->bits_per_sample_); | ||||||
|       return 0; |       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; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     const uint8_t *data = data_event.data; | ||||||
|  |     size_t remaining = data_event.len; | ||||||
|  |     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) { | ||||||
|       size_t bytes_written; |       size_t bytes_written; | ||||||
|  |       esp_err_t err = | ||||||
|     memmove(buffer, data_event.data, data_event.len); |           i2s_write(this_speaker->parent_->get_port(), data, remaining, &bytes_written, (32 / portTICK_PERIOD_MS)); | ||||||
|     size_t remaining = data_event.len / 2; |  | ||||||
|     size_t current = 0; |  | ||||||
|  |  | ||||||
|     while (remaining > 0) { |  | ||||||
|       uint32_t sample = (buffer[current] << 16) | (buffer[current] & 0xFFFF); |  | ||||||
|  |  | ||||||
|       esp_err_t err = i2s_write(this_speaker->parent_->get_port(), &sample, sizeof(sample), &bytes_written, |  | ||||||
|                                 (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 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user