mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	[i2s_audio] Bugfix: Follow configured bits per sample (#7916)
This commit is contained in:
		| @@ -33,14 +33,15 @@ enum SpeakerEventGroupBits : uint32_t { | |||||||
|   STATE_RUNNING = (1 << 11), |   STATE_RUNNING = (1 << 11), | ||||||
|   STATE_STOPPING = (1 << 12), |   STATE_STOPPING = (1 << 12), | ||||||
|   STATE_STOPPED = (1 << 13), |   STATE_STOPPED = (1 << 13), | ||||||
|   ERR_INVALID_FORMAT = (1 << 14), |   ERR_TASK_FAILED_TO_START = (1 << 14), | ||||||
|   ERR_TASK_FAILED_TO_START = (1 << 15), |   ERR_ESP_INVALID_STATE = (1 << 15), | ||||||
|   ERR_ESP_INVALID_STATE = (1 << 16), |   ERR_ESP_NOT_SUPPORTED = (1 << 16), | ||||||
|   ERR_ESP_INVALID_ARG = (1 << 17), |   ERR_ESP_INVALID_ARG = (1 << 17), | ||||||
|   ERR_ESP_INVALID_SIZE = (1 << 18), |   ERR_ESP_INVALID_SIZE = (1 << 18), | ||||||
|   ERR_ESP_NO_MEM = (1 << 19), |   ERR_ESP_NO_MEM = (1 << 19), | ||||||
|   ERR_ESP_FAIL = (1 << 20), |   ERR_ESP_FAIL = (1 << 20), | ||||||
|   ALL_ERR_ESP_BITS = ERR_ESP_INVALID_STATE | ERR_ESP_INVALID_ARG | ERR_ESP_INVALID_SIZE | ERR_ESP_NO_MEM | ERR_ESP_FAIL, |   ALL_ERR_ESP_BITS = ERR_ESP_INVALID_STATE | ERR_ESP_NOT_SUPPORTED | ERR_ESP_INVALID_ARG | ERR_ESP_INVALID_SIZE | | ||||||
|  |                      ERR_ESP_NO_MEM | ERR_ESP_FAIL, | ||||||
|   ALL_BITS = 0x00FFFFFF,  // All valid FreeRTOS event group bits |   ALL_BITS = 0x00FFFFFF,  // All valid FreeRTOS event group bits | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -55,6 +56,8 @@ static esp_err_t err_bit_to_esp_err(uint32_t bit) { | |||||||
|       return ESP_ERR_INVALID_SIZE; |       return ESP_ERR_INVALID_SIZE; | ||||||
|     case SpeakerEventGroupBits::ERR_ESP_NO_MEM: |     case SpeakerEventGroupBits::ERR_ESP_NO_MEM: | ||||||
|       return ESP_ERR_NO_MEM; |       return ESP_ERR_NO_MEM; | ||||||
|  |     case SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED: | ||||||
|  |       return ESP_ERR_NOT_SUPPORTED; | ||||||
|     default: |     default: | ||||||
|       return ESP_FAIL; |       return ESP_FAIL; | ||||||
|   } |   } | ||||||
| @@ -135,19 +138,19 @@ void I2SAudioSpeaker::loop() { | |||||||
|     xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::ERR_TASK_FAILED_TO_START); |     xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::ERR_TASK_FAILED_TO_START); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (event_group_bits & SpeakerEventGroupBits::ERR_INVALID_FORMAT) { |   if (event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS) { | ||||||
|  |     uint32_t error_bits = event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS; | ||||||
|  |     ESP_LOGW(TAG, "Error writing to I2S: %s", esp_err_to_name(err_bit_to_esp_err(error_bits))); | ||||||
|  |     this->status_set_warning(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (event_group_bits & SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED) { | ||||||
|     this->status_set_error("Failed to adjust I2S bus to match the incoming audio"); |     this->status_set_error("Failed to adjust I2S bus to match the incoming audio"); | ||||||
|     ESP_LOGE(TAG, |     ESP_LOGE(TAG, | ||||||
|              "Incompatible audio format: sample rate = %" PRIu32 ", channels = %" PRIu8 ", bits per sample = %" PRIu8, |              "Incompatible audio format: sample rate = %" PRIu32 ", channels = %" PRIu8 ", bits per sample = %" PRIu8, | ||||||
|              this->audio_stream_info_.sample_rate, this->audio_stream_info_.channels, |              this->audio_stream_info_.sample_rate, this->audio_stream_info_.channels, | ||||||
|              this->audio_stream_info_.bits_per_sample); |              this->audio_stream_info_.bits_per_sample); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS) { |  | ||||||
|     uint32_t error_bits = event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS; |  | ||||||
|     ESP_LOGW(TAG, "Error writing to I2S: %s", esp_err_to_name(err_bit_to_esp_err(error_bits))); |  | ||||||
|     this->status_set_warning(); |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void I2SAudioSpeaker::set_volume(float volume) { | void I2SAudioSpeaker::set_volume(float volume) { | ||||||
| @@ -236,13 +239,14 @@ void I2SAudioSpeaker::speaker_task(void *params) { | |||||||
|   xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STARTING); |   xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STARTING); | ||||||
|  |  | ||||||
|   audio::AudioStreamInfo audio_stream_info = this_speaker->audio_stream_info_; |   audio::AudioStreamInfo audio_stream_info = this_speaker->audio_stream_info_; | ||||||
|   const ssize_t bytes_per_sample = audio_stream_info.get_bytes_per_sample(); |  | ||||||
|   const uint8_t number_of_channels = audio_stream_info.channels; |  | ||||||
|  |  | ||||||
|   const size_t dma_buffers_size = DMA_BUFFERS_COUNT * DMA_BUFFER_DURATION_MS * this_speaker->sample_rate_ / 1000 * |   const uint32_t bytes_per_ms = | ||||||
|                                   bytes_per_sample * number_of_channels; |       audio_stream_info.channels * audio_stream_info.get_bytes_per_sample() * audio_stream_info.sample_rate / 1000; | ||||||
|   const size_t ring_buffer_size = |  | ||||||
|       this_speaker->buffer_duration_ms_ * this_speaker->sample_rate_ / 1000 * bytes_per_sample * number_of_channels; |   const size_t dma_buffers_size = DMA_BUFFERS_COUNT * DMA_BUFFER_DURATION_MS * bytes_per_ms; | ||||||
|  |  | ||||||
|  |   // Ensure ring buffer is at least as large as the total size of the DMA buffers | ||||||
|  |   const size_t ring_buffer_size = std::min(dma_buffers_size, this_speaker->buffer_duration_ms_ * bytes_per_ms); | ||||||
|  |  | ||||||
|   if (this_speaker->send_esp_err_to_event_group_(this_speaker->allocate_buffers_(dma_buffers_size, ring_buffer_size))) { |   if (this_speaker->send_esp_err_to_event_group_(this_speaker->allocate_buffers_(dma_buffers_size, ring_buffer_size))) { | ||||||
|     // Failed to allocate buffers |     // Failed to allocate buffers | ||||||
| @@ -250,14 +254,7 @@ void I2SAudioSpeaker::speaker_task(void *params) { | |||||||
|     this_speaker->delete_task_(dma_buffers_size); |     this_speaker->delete_task_(dma_buffers_size); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (this_speaker->send_esp_err_to_event_group_(this_speaker->start_i2s_driver_())) { |   if (!this_speaker->send_esp_err_to_event_group_(this_speaker->start_i2s_driver_(audio_stream_info))) { | ||||||
|     // Failed to start I2S driver |  | ||||||
|     this_speaker->delete_task_(dma_buffers_size); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (!this_speaker->send_esp_err_to_event_group_(this_speaker->reconfigure_i2s_stream_info_(audio_stream_info))) { |  | ||||||
|     // Successfully set the I2S stream info, ready to write audio data to the I2S port |  | ||||||
|  |  | ||||||
|     xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_RUNNING); |     xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_RUNNING); | ||||||
|  |  | ||||||
|     bool stop_gracefully = false; |     bool stop_gracefully = false; | ||||||
| @@ -275,6 +272,12 @@ void I2SAudioSpeaker::speaker_task(void *params) { | |||||||
|         stop_gracefully = true; |         stop_gracefully = true; | ||||||
|       } |       } | ||||||
|  |  | ||||||
|  |       if (this_speaker->audio_stream_info_ != audio_stream_info) { | ||||||
|  |         // Audio stream info has changed, stop the speaker task so it will restart with the proper settings. | ||||||
|  |  | ||||||
|  |         break; | ||||||
|  |       } | ||||||
|  |  | ||||||
|       i2s_event_t i2s_event; |       i2s_event_t i2s_event; | ||||||
|       while (xQueueReceive(this_speaker->i2s_event_queue_, &i2s_event, 0)) { |       while (xQueueReceive(this_speaker->i2s_event_queue_, &i2s_event, 0)) { | ||||||
|         if (i2s_event.type == I2S_EVENT_TX_Q_OVF) { |         if (i2s_event.type == I2S_EVENT_TX_Q_OVF) { | ||||||
| @@ -316,17 +319,14 @@ void I2SAudioSpeaker::speaker_task(void *params) { | |||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } else { |  | ||||||
|     // Couldn't configure the I2S port to be compatible with the incoming audio |     xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STOPPING); | ||||||
|     xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::ERR_INVALID_FORMAT); |  | ||||||
|  |     i2s_driver_uninstall(this_speaker->parent_->get_port()); | ||||||
|  |  | ||||||
|  |     this_speaker->parent_->unlock(); | ||||||
|   } |   } | ||||||
|   i2s_zero_dma_buffer(this_speaker->parent_->get_port()); |  | ||||||
|  |  | ||||||
|   xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STOPPING); |  | ||||||
|  |  | ||||||
|   i2s_driver_uninstall(this_speaker->parent_->get_port()); |  | ||||||
|  |  | ||||||
|   this_speaker->parent_->unlock(); |  | ||||||
|   this_speaker->delete_task_(dma_buffers_size); |   this_speaker->delete_task_(dma_buffers_size); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -382,6 +382,9 @@ bool I2SAudioSpeaker::send_esp_err_to_event_group_(esp_err_t err) { | |||||||
|     case ESP_ERR_NO_MEM: |     case ESP_ERR_NO_MEM: | ||||||
|       xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_NO_MEM); |       xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_NO_MEM); | ||||||
|       return true; |       return true; | ||||||
|  |     case ESP_ERR_NOT_SUPPORTED: | ||||||
|  |       xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED); | ||||||
|  |       return true; | ||||||
|     default: |     default: | ||||||
|       xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_FAIL); |       xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_FAIL); | ||||||
|       return true; |       return true; | ||||||
| @@ -411,18 +414,40 @@ esp_err_t I2SAudioSpeaker::allocate_buffers_(size_t data_buffer_size, size_t rin | |||||||
|   return ESP_OK; |   return ESP_OK; | ||||||
| } | } | ||||||
|  |  | ||||||
| esp_err_t I2SAudioSpeaker::start_i2s_driver_() { | esp_err_t I2SAudioSpeaker::start_i2s_driver_(audio::AudioStreamInfo &audio_stream_info) { | ||||||
|  |   if ((this->i2s_mode_ & I2S_MODE_SLAVE) && (this->sample_rate_ != audio_stream_info.sample_rate)) {  // NOLINT | ||||||
|  |     //  Can't reconfigure I2S bus, so the sample rate must match the configured value | ||||||
|  |     return ESP_ERR_NOT_SUPPORTED; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if ((i2s_bits_per_sample_t) audio_stream_info.bits_per_sample > this->bits_per_sample_) { | ||||||
|  |     // Currently can't handle the case when the incoming audio has more bits per sample than the configured value | ||||||
|  |     return ESP_ERR_NOT_SUPPORTED; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   if (!this->parent_->try_lock()) { |   if (!this->parent_->try_lock()) { | ||||||
|     return ESP_ERR_INVALID_STATE; |     return ESP_ERR_INVALID_STATE; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   i2s_channel_fmt_t channel = this->channel_; | ||||||
|  |  | ||||||
|  |   if (audio_stream_info.channels == 1) { | ||||||
|  |     if (this->channel_ == I2S_CHANNEL_FMT_ONLY_LEFT) { | ||||||
|  |       channel = I2S_CHANNEL_FMT_ONLY_LEFT; | ||||||
|  |     } else { | ||||||
|  |       channel = I2S_CHANNEL_FMT_ONLY_RIGHT; | ||||||
|  |     } | ||||||
|  |   } else if (audio_stream_info.channels == 2) { | ||||||
|  |     channel = I2S_CHANNEL_FMT_RIGHT_LEFT; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   int dma_buffer_length = DMA_BUFFER_DURATION_MS * this->sample_rate_ / 1000; |   int dma_buffer_length = DMA_BUFFER_DURATION_MS * this->sample_rate_ / 1000; | ||||||
|  |  | ||||||
|   i2s_driver_config_t config = { |   i2s_driver_config_t config = { | ||||||
|     .mode = (i2s_mode_t) (this->i2s_mode_ | I2S_MODE_TX), |     .mode = (i2s_mode_t) (this->i2s_mode_ | I2S_MODE_TX), | ||||||
|     .sample_rate = this->sample_rate_, |     .sample_rate = audio_stream_info.sample_rate, | ||||||
|     .bits_per_sample = this->bits_per_sample_, |     .bits_per_sample = this->bits_per_sample_, | ||||||
|     .channel_format = this->channel_, |     .channel_format = channel, | ||||||
|     .communication_format = this->i2s_comm_fmt_, |     .communication_format = this->i2s_comm_fmt_, | ||||||
|     .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, |     .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, | ||||||
|     .dma_buf_count = DMA_BUFFERS_COUNT, |     .dma_buf_count = DMA_BUFFERS_COUNT, | ||||||
| @@ -477,30 +502,6 @@ esp_err_t I2SAudioSpeaker::start_i2s_driver_() { | |||||||
|   return err; |   return err; | ||||||
| } | } | ||||||
|  |  | ||||||
| esp_err_t I2SAudioSpeaker::reconfigure_i2s_stream_info_(audio::AudioStreamInfo &audio_stream_info) { |  | ||||||
|   if (this->i2s_mode_ & I2S_MODE_MASTER) { |  | ||||||
|     // ESP controls for the the I2S bus, so adjust the sample rate and bits per sample to match the incoming audio |  | ||||||
|     this->sample_rate_ = audio_stream_info.sample_rate; |  | ||||||
|     this->bits_per_sample_ = (i2s_bits_per_sample_t) audio_stream_info.bits_per_sample; |  | ||||||
|   } else if (this->sample_rate_ != audio_stream_info.sample_rate) { |  | ||||||
|     // Can't reconfigure I2S bus, so the sample rate must match the configured value |  | ||||||
|     return ESP_ERR_INVALID_ARG; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if ((i2s_bits_per_sample_t) audio_stream_info.bits_per_sample > this->bits_per_sample_) { |  | ||||||
|     // Currently can't handle the case when the incoming audio has more bits per sample than the configured value |  | ||||||
|     return ESP_ERR_INVALID_ARG; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (audio_stream_info.channels == 1) { |  | ||||||
|     return i2s_set_clk(this->parent_->get_port(), this->sample_rate_, this->bits_per_sample_, I2S_CHANNEL_MONO); |  | ||||||
|   } else if (audio_stream_info.channels == 2) { |  | ||||||
|     return i2s_set_clk(this->parent_->get_port(), this->sample_rate_, this->bits_per_sample_, I2S_CHANNEL_STEREO); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return ESP_ERR_INVALID_ARG; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void I2SAudioSpeaker::delete_task_(size_t buffer_size) { | void I2SAudioSpeaker::delete_task_(size_t buffer_size) { | ||||||
|   this->audio_ring_buffer_.reset();  // Releases onwership of the shared_ptr |   this->audio_ring_buffer_.reset();  // Releases onwership of the shared_ptr | ||||||
|  |  | ||||||
|   | |||||||
| @@ -91,24 +91,15 @@ class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Comp | |||||||
|   esp_err_t allocate_buffers_(size_t data_buffer_size, size_t ring_buffer_size); |   esp_err_t allocate_buffers_(size_t data_buffer_size, size_t ring_buffer_size); | ||||||
|  |  | ||||||
|   /// @brief Starts the ESP32 I2S driver. |   /// @brief Starts the ESP32 I2S driver. | ||||||
|   /// Attempts to lock the I2S port, starts the I2S driver, and sets the data out pin. If it fails, it will unlock |   /// Attempts to lock the I2S port, starts the I2S driver using the passed in stream information, and sets the data out | ||||||
|   /// the I2S port and uninstall the driver, if necessary. |   /// pin. If it fails, it will unlock the I2S port and uninstall the driver, if necessary. | ||||||
|   /// @return ESP_ERR_INVALID_STATE if the I2S port is already locked. |   /// @param audio_stream_info Stream information for the I2S driver. | ||||||
|   ///         ESP_ERR_INVALID_ARG if installing the driver or setting the data out pin fails due to a parameter error. |   /// @return ESP_ERR_NOT_ALLOWED if the I2S port can't play the incoming audio stream. | ||||||
|  |   ///         ESP_ERR_INVALID_STATE if the I2S port is already locked. | ||||||
|  |   ///         ESP_ERR_INVALID_ARG if nstalling the driver or setting the data outpin fails due to a parameter error. | ||||||
|   ///         ESP_ERR_NO_MEM if the driver fails to install due to a memory allocation error. |   ///         ESP_ERR_NO_MEM if the driver fails to install due to a memory allocation error. | ||||||
|   ///         ESP_FAIL if setting the data out pin fails due to an IO error |   ///         ESP_FAIL if setting the data out pin fails due to an IO error ESP_OK if successful | ||||||
|   ///         ESP_OK if successful |   esp_err_t start_i2s_driver_(audio::AudioStreamInfo &audio_stream_info); | ||||||
|   esp_err_t start_i2s_driver_(); |  | ||||||
|  |  | ||||||
|   /// @brief Adjusts the I2S driver configuration to match the incoming audio stream. |  | ||||||
|   /// Modifies I2S driver's sample rate, bits per sample, and number of channel settings. If the I2S is in secondary |  | ||||||
|   /// mode, it only modifies the number of channels. |  | ||||||
|   /// @param audio_stream_info  Describes the incoming audio stream |  | ||||||
|   /// @return ESP_ERR_INVALID_ARG if there is a parameter error, if there is more than 2 channels in the stream, or if |  | ||||||
|   ///           the audio settings are incompatible with the configuration. |  | ||||||
|   ///         ESP_ERR_NO_MEM if the driver fails to reconfigure due to a memory allocation error. |  | ||||||
|   ///         ESP_OK if successful. |  | ||||||
|   esp_err_t reconfigure_i2s_stream_info_(audio::AudioStreamInfo &audio_stream_info); |  | ||||||
|  |  | ||||||
|   /// @brief Deletes the speaker's task. |   /// @brief Deletes the speaker's task. | ||||||
|   /// Deallocates the data_buffer_ and audio_ring_buffer_, if necessary, and deletes the task. Should only be called by |   /// Deallocates the data_buffer_ and audio_ring_buffer_, if necessary, and deletes the task. Should only be called by | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user