1
0
mirror of https://github.com/esphome/esphome.git synced 2025-01-18 12:05:41 +00:00

[i2s_audio] Bugfix: Follow configured bits per sample (#7916)

This commit is contained in:
Kevin Ahrendt 2024-12-04 16:18:14 -05:00 committed by GitHub
parent 016fac2496
commit 472402745d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 70 additions and 78 deletions

View File

@ -33,14 +33,15 @@ enum SpeakerEventGroupBits : uint32_t {
STATE_RUNNING = (1 << 11),
STATE_STOPPING = (1 << 12),
STATE_STOPPED = (1 << 13),
ERR_INVALID_FORMAT = (1 << 14),
ERR_TASK_FAILED_TO_START = (1 << 15),
ERR_ESP_INVALID_STATE = (1 << 16),
ERR_TASK_FAILED_TO_START = (1 << 14),
ERR_ESP_INVALID_STATE = (1 << 15),
ERR_ESP_NOT_SUPPORTED = (1 << 16),
ERR_ESP_INVALID_ARG = (1 << 17),
ERR_ESP_INVALID_SIZE = (1 << 18),
ERR_ESP_NO_MEM = (1 << 19),
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
};
@ -55,6 +56,8 @@ static esp_err_t err_bit_to_esp_err(uint32_t bit) {
return ESP_ERR_INVALID_SIZE;
case SpeakerEventGroupBits::ERR_ESP_NO_MEM:
return ESP_ERR_NO_MEM;
case SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED:
return ESP_ERR_NOT_SUPPORTED;
default:
return ESP_FAIL;
}
@ -135,19 +138,19 @@ void I2SAudioSpeaker::loop() {
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");
ESP_LOGE(TAG,
"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_.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) {
@ -236,13 +239,14 @@ void I2SAudioSpeaker::speaker_task(void *params) {
xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STARTING);
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 *
bytes_per_sample * number_of_channels;
const size_t ring_buffer_size =
this_speaker->buffer_duration_ms_ * this_speaker->sample_rate_ / 1000 * bytes_per_sample * number_of_channels;
const uint32_t bytes_per_ms =
audio_stream_info.channels * audio_stream_info.get_bytes_per_sample() * audio_stream_info.sample_rate / 1000;
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))) {
// Failed to allocate buffers
@ -250,14 +254,7 @@ void I2SAudioSpeaker::speaker_task(void *params) {
this_speaker->delete_task_(dma_buffers_size);
}
if (this_speaker->send_esp_err_to_event_group_(this_speaker->start_i2s_driver_())) {
// 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
if (!this_speaker->send_esp_err_to_event_group_(this_speaker->start_i2s_driver_(audio_stream_info))) {
xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_RUNNING);
bool stop_gracefully = false;
@ -275,6 +272,12 @@ void I2SAudioSpeaker::speaker_task(void *params) {
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;
while (xQueueReceive(this_speaker->i2s_event_queue_, &i2s_event, 0)) {
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::ERR_INVALID_FORMAT);
xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STOPPING);
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);
}
@ -382,6 +382,9 @@ bool I2SAudioSpeaker::send_esp_err_to_event_group_(esp_err_t err) {
case ESP_ERR_NO_MEM:
xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_NO_MEM);
return true;
case ESP_ERR_NOT_SUPPORTED:
xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED);
return true;
default:
xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_FAIL);
return true;
@ -411,18 +414,40 @@ esp_err_t I2SAudioSpeaker::allocate_buffers_(size_t data_buffer_size, size_t rin
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()) {
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;
i2s_driver_config_t config = {
.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_,
.channel_format = this->channel_,
.channel_format = channel,
.communication_format = this->i2s_comm_fmt_,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = DMA_BUFFERS_COUNT,
@ -477,30 +502,6 @@ esp_err_t I2SAudioSpeaker::start_i2s_driver_() {
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) {
this->audio_ring_buffer_.reset(); // Releases onwership of the shared_ptr

View File

@ -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);
/// @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
/// the I2S port and uninstall the driver, if necessary.
/// @return ESP_ERR_INVALID_STATE if the I2S port is already locked.
/// ESP_ERR_INVALID_ARG if installing the driver or setting the data out pin fails due to a parameter error.
/// Attempts to lock the I2S port, starts the I2S driver using the passed in stream information, and sets the data out
/// pin. If it fails, it will unlock the I2S port and uninstall the driver, if necessary.
/// @param audio_stream_info Stream information for the I2S driver.
/// @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_FAIL if setting the data out pin fails due to an IO error
/// ESP_OK if successful
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);
/// ESP_FAIL if setting the data out pin fails due to an IO error ESP_OK if successful
esp_err_t start_i2s_driver_(audio::AudioStreamInfo &audio_stream_info);
/// @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