diff --git a/CODEOWNERS b/CODEOWNERS index 471def542b..f8cdfdc6c6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -416,6 +416,7 @@ esphome/components/resampler/speaker/* @kahrendt esphome/components/restart/* @esphome/core esphome/components/rf_bridge/* @jesserockz esphome/components/rgbct/* @jesserockz +esphome/components/ring_buffer/* @kahrendt esphome/components/rp2040/* @jesserockz esphome/components/rp2040_ble/* @bdraco esphome/components/rp2040_pio_led_strip/* @Papa-DMan diff --git a/esphome/components/audio/__init__.py b/esphome/components/audio/__init__.py index 67ef2e7d1a..44371e87ab 100644 --- a/esphome/components/audio/__init__.py +++ b/esphome/components/audio/__init__.py @@ -16,6 +16,7 @@ from esphome.const import ( from esphome.core import CORE import esphome.final_validate as fv +AUTO_LOAD = ["ring_buffer"] CODEOWNERS = ["@kahrendt"] DOMAIN = "audio" audio_ns = cg.esphome_ns.namespace("audio") diff --git a/esphome/components/audio/audio_decoder.cpp b/esphome/components/audio/audio_decoder.cpp index 3e6fad1101..d4ff59fc36 100644 --- a/esphome/components/audio/audio_decoder.cpp +++ b/esphome/components/audio/audio_decoder.cpp @@ -19,7 +19,7 @@ AudioDecoder::AudioDecoder(size_t input_buffer_size, size_t output_buffer_size) this->output_transfer_buffer_ = AudioSinkTransferBuffer::create(output_buffer_size); } -esp_err_t AudioDecoder::add_source(std::weak_ptr &input_ring_buffer) { +esp_err_t AudioDecoder::add_source(std::weak_ptr &input_ring_buffer) { auto source = AudioSourceTransferBuffer::create(this->input_buffer_size_); if (source == nullptr) { return ESP_ERR_NO_MEM; @@ -36,7 +36,7 @@ esp_err_t AudioDecoder::add_source(const uint8_t *data_pointer, size_t length) { return ESP_OK; } -esp_err_t AudioDecoder::add_sink(std::weak_ptr &output_ring_buffer) { +esp_err_t AudioDecoder::add_sink(std::weak_ptr &output_ring_buffer) { if (this->output_transfer_buffer_ != nullptr) { this->output_transfer_buffer_->set_sink(output_ring_buffer); return ESP_OK; diff --git a/esphome/components/audio/audio_decoder.h b/esphome/components/audio/audio_decoder.h index 7ea7a824f9..c34ebbc613 100644 --- a/esphome/components/audio/audio_decoder.h +++ b/esphome/components/audio/audio_decoder.h @@ -5,9 +5,9 @@ #include "audio.h" #include "audio_transfer_buffer.h" +#include "esphome/components/ring_buffer/ring_buffer.h" #include "esphome/core/defines.h" #include "esphome/core/helpers.h" -#include "esphome/core/ring_buffer.h" #ifdef USE_SPEAKER #include "esphome/components/speaker/speaker.h" @@ -70,12 +70,12 @@ class AudioDecoder { /// @brief Adds a source ring buffer for raw file data. Takes ownership of the ring buffer in a shared_ptr. /// @param input_ring_buffer weak_ptr of a shared_ptr of the sink ring buffer to transfer ownership /// @return ESP_OK if successsful, ESP_ERR_NO_MEM if the transfer buffer wasn't allocated - esp_err_t add_source(std::weak_ptr &input_ring_buffer); + esp_err_t add_source(std::weak_ptr &input_ring_buffer); /// @brief Adds a sink ring buffer for decoded audio. Takes ownership of the ring buffer in a shared_ptr. /// @param output_ring_buffer weak_ptr of a shared_ptr of the sink ring buffer to transfer ownership /// @return ESP_OK if successsful, ESP_ERR_NO_MEM if the transfer buffer wasn't allocated - esp_err_t add_sink(std::weak_ptr &output_ring_buffer); + esp_err_t add_sink(std::weak_ptr &output_ring_buffer); #ifdef USE_SPEAKER /// @brief Adds a sink speaker for decoded audio. diff --git a/esphome/components/audio/audio_reader.cpp b/esphome/components/audio/audio_reader.cpp index 500f20533c..4678ed548c 100644 --- a/esphome/components/audio/audio_reader.cpp +++ b/esphome/components/audio/audio_reader.cpp @@ -54,7 +54,7 @@ enum HttpStatus { AudioReader::~AudioReader() { this->cleanup_connection_(); } -esp_err_t AudioReader::add_sink(const std::weak_ptr &output_ring_buffer) { +esp_err_t AudioReader::add_sink(const std::weak_ptr &output_ring_buffer) { if (current_audio_file_ != nullptr) { // A transfer buffer isn't ncessary for a local file this->file_ring_buffer_ = output_ring_buffer.lock(); diff --git a/esphome/components/audio/audio_reader.h b/esphome/components/audio/audio_reader.h index 61f187d151..b1f76172b0 100644 --- a/esphome/components/audio/audio_reader.h +++ b/esphome/components/audio/audio_reader.h @@ -5,7 +5,7 @@ #include "audio.h" #include "audio_transfer_buffer.h" -#include "esphome/core/ring_buffer.h" +#include "esphome/components/ring_buffer/ring_buffer.h" #include "esp_err.h" @@ -35,7 +35,7 @@ class AudioReader { /// @brief Adds a sink ring buffer for audio data. Takes ownership of the ring buffer in a shared_ptr /// @param output_ring_buffer weak_ptr of a shared_ptr of the sink ring buffer to transfer ownership /// @return ESP_OK if successful, ESP_ERR_INVALID_STATE otherwise - esp_err_t add_sink(const std::weak_ptr &output_ring_buffer); + esp_err_t add_sink(const std::weak_ptr &output_ring_buffer); /// @brief Starts reading an audio file from an http source. The transfer buffer is allocated here. /// @param uri Web url to the http file. @@ -60,7 +60,7 @@ class AudioReader { AudioReaderState file_read_(); AudioReaderState http_read_(); - std::shared_ptr file_ring_buffer_; + std::shared_ptr file_ring_buffer_; std::unique_ptr output_transfer_buffer_; void cleanup_connection_(); diff --git a/esphome/components/audio/audio_resampler.cpp b/esphome/components/audio/audio_resampler.cpp index ac1039971e..c04cc881f5 100644 --- a/esphome/components/audio/audio_resampler.cpp +++ b/esphome/components/audio/audio_resampler.cpp @@ -16,7 +16,7 @@ AudioResampler::AudioResampler(size_t input_buffer_size, size_t output_buffer_si this->output_transfer_buffer_ = AudioSinkTransferBuffer::create(output_buffer_size); } -esp_err_t AudioResampler::add_source(std::weak_ptr &input_ring_buffer) { +esp_err_t AudioResampler::add_source(std::weak_ptr &input_ring_buffer) { if (this->input_transfer_buffer_ != nullptr) { this->input_transfer_buffer_->set_source(input_ring_buffer); return ESP_OK; @@ -24,7 +24,7 @@ esp_err_t AudioResampler::add_source(std::weak_ptr &input_ring_buffe return ESP_ERR_NO_MEM; } -esp_err_t AudioResampler::add_sink(std::weak_ptr &output_ring_buffer) { +esp_err_t AudioResampler::add_sink(std::weak_ptr &output_ring_buffer) { if (this->output_transfer_buffer_ != nullptr) { this->output_transfer_buffer_->set_sink(output_ring_buffer); return ESP_OK; diff --git a/esphome/components/audio/audio_resampler.h b/esphome/components/audio/audio_resampler.h index e7503d1de0..575ad13692 100644 --- a/esphome/components/audio/audio_resampler.h +++ b/esphome/components/audio/audio_resampler.h @@ -5,9 +5,9 @@ #include "audio.h" #include "audio_transfer_buffer.h" +#include "esphome/components/ring_buffer/ring_buffer.h" #include "esphome/core/defines.h" #include "esphome/core/helpers.h" -#include "esphome/core/ring_buffer.h" #ifdef USE_SPEAKER #include "esphome/components/speaker/speaker.h" @@ -40,12 +40,12 @@ class AudioResampler { /// @brief Adds a source ring buffer for audio data. Takes ownership of the ring buffer in a shared_ptr. /// @param input_ring_buffer weak_ptr of a shared_ptr of the sink ring buffer to transfer ownership /// @return ESP_OK if successsful, ESP_ERR_NO_MEM if the transfer buffer wasn't allocated - esp_err_t add_source(std::weak_ptr &input_ring_buffer); + esp_err_t add_source(std::weak_ptr &input_ring_buffer); /// @brief Adds a sink ring buffer for resampled audio. Takes ownership of the ring buffer in a shared_ptr. /// @param output_ring_buffer weak_ptr of a shared_ptr of the sink ring buffer to transfer ownership /// @return ESP_OK if successsful, ESP_ERR_NO_MEM if the transfer buffer wasn't allocated - esp_err_t add_sink(std::weak_ptr &output_ring_buffer); + esp_err_t add_sink(std::weak_ptr &output_ring_buffer); #ifdef USE_SPEAKER /// @brief Adds a sink speaker for decoded audio. diff --git a/esphome/components/audio/audio_transfer_buffer.h b/esphome/components/audio/audio_transfer_buffer.h index 7aa830fafa..68151bf4e2 100644 --- a/esphome/components/audio/audio_transfer_buffer.h +++ b/esphome/components/audio/audio_transfer_buffer.h @@ -1,8 +1,8 @@ #pragma once #ifdef USE_ESP32 +#include "esphome/components/ring_buffer/ring_buffer.h" #include "esphome/core/defines.h" -#include "esphome/core/ring_buffer.h" #ifdef USE_SPEAKER #include "esphome/components/speaker/speaker.h" @@ -76,7 +76,7 @@ class AudioTransferBuffer { void deallocate_buffer_(); // A possible source or sink for the transfer buffer - std::shared_ptr ring_buffer_; + std::shared_ptr ring_buffer_; uint8_t *buffer_{nullptr}; uint8_t *data_start_{nullptr}; @@ -105,7 +105,7 @@ class AudioSinkTransferBuffer : public AudioTransferBuffer { /// @brief Adds a ring buffer as the transfer buffer's sink. /// @param ring_buffer weak_ptr to the allocated ring buffer - void set_sink(const std::weak_ptr &ring_buffer) { this->ring_buffer_ = ring_buffer.lock(); } + void set_sink(const std::weak_ptr &ring_buffer) { this->ring_buffer_ = ring_buffer.lock(); } #ifdef USE_SPEAKER /// @brief Adds a speaker as the transfer buffer's sink. @@ -179,7 +179,9 @@ class AudioSourceTransferBuffer : public AudioTransferBuffer, public AudioReadab /// @brief Adds a ring buffer as the transfer buffer's source. /// @param ring_buffer weak_ptr to the allocated ring buffer - void set_source(const std::weak_ptr &ring_buffer) { this->ring_buffer_ = ring_buffer.lock(); }; + void set_source(const std::weak_ptr &ring_buffer) { + this->ring_buffer_ = ring_buffer.lock(); + }; // AudioReadableBuffer interface const uint8_t *data() const override { return this->data_start_; } diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp index 58d17ea6c4..a71b7db3ba 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp @@ -195,7 +195,7 @@ size_t I2SAudioSpeakerBase::play(const uint8_t *data, size_t length, TickType_t size_t bytes_written = 0; if (this->state_ == speaker::STATE_RUNNING) { - std::shared_ptr temp_ring_buffer = this->audio_ring_buffer_.lock(); + std::shared_ptr temp_ring_buffer = this->audio_ring_buffer_.lock(); if (temp_ring_buffer != nullptr) { // The weak_ptr locks successfully only while the speaker task owns the ring buffer, so it is safe to write bytes_written = temp_ring_buffer->write_without_replacement((void *) data, length, ticks_to_wait); @@ -207,7 +207,7 @@ size_t I2SAudioSpeakerBase::play(const uint8_t *data, size_t length, TickType_t bool I2SAudioSpeakerBase::has_buffered_data() const { if (this->audio_ring_buffer_.use_count() > 0) { - std::shared_ptr temp_ring_buffer = this->audio_ring_buffer_.lock(); + std::shared_ptr temp_ring_buffer = this->audio_ring_buffer_.lock(); return temp_ring_buffer->available() > 0; } return false; diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h index d9a228ef2c..c598ca1bf8 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h @@ -9,12 +9,12 @@ #include #include "esphome/components/audio/audio.h" +#include "esphome/components/ring_buffer/ring_buffer.h" #include "esphome/components/speaker/speaker.h" #include "esphome/core/component.h" #include "esphome/core/gpio.h" #include "esphome/core/helpers.h" -#include "esphome/core/ring_buffer.h" namespace esphome::i2s_audio { @@ -143,7 +143,7 @@ class I2SAudioSpeakerBase : public I2SAudioOut, public speaker::Speaker, public QueueHandle_t i2s_event_queue_{nullptr}; - std::weak_ptr audio_ring_buffer_; + std::weak_ptr audio_ring_buffer_; uint32_t buffer_duration_ms_; diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker_standard.cpp b/esphome/components/i2s_audio/speaker/i2s_audio_speaker_standard.cpp index edb316e3a2..51f2b225e2 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker_standard.cpp +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker_standard.cpp @@ -54,7 +54,7 @@ void I2SAudioSpeaker::run_speaker_task() { audio::AudioSourceTransferBuffer::create(bytes_to_fill_single_dma_buffer); if (transfer_buffer != nullptr) { - std::shared_ptr temp_ring_buffer = RingBuffer::create(ring_buffer_size); + std::shared_ptr temp_ring_buffer = ring_buffer::RingBuffer::create(ring_buffer_size); if (temp_ring_buffer.use_count() == 1) { transfer_buffer->set_source(temp_ring_buffer); this->audio_ring_buffer_ = temp_ring_buffer; diff --git a/esphome/components/micro_wake_word/__init__.py b/esphome/components/micro_wake_word/__init__.py index 22d2098de0..38926fce99 100644 --- a/esphome/components/micro_wake_word/__init__.py +++ b/esphome/components/micro_wake_word/__init__.py @@ -30,6 +30,7 @@ from esphome.core import CORE, HexInt _LOGGER = logging.getLogger(__name__) +AUTO_LOAD = ["ring_buffer"] CODEOWNERS = ["@kahrendt", "@jesserockz"] DEPENDENCIES = ["microphone"] diff --git a/esphome/components/micro_wake_word/micro_wake_word.cpp b/esphome/components/micro_wake_word/micro_wake_word.cpp index 1568fc6373..c031a9f269 100644 --- a/esphome/components/micro_wake_word/micro_wake_word.cpp +++ b/esphome/components/micro_wake_word/micro_wake_word.cpp @@ -106,7 +106,7 @@ void MicroWakeWord::setup() { if (this->state_ == State::STOPPED) { return; } - std::shared_ptr temp_ring_buffer = this->ring_buffer_.lock(); + std::shared_ptr temp_ring_buffer = this->ring_buffer_.lock(); if (this->ring_buffer_.use_count() > 1) { size_t bytes_free = temp_ring_buffer->free(); @@ -156,7 +156,7 @@ void MicroWakeWord::inference_task(void *params) { if (!(xEventGroupGetBits(this_mww->event_group_) & ERROR_BITS)) { // Allocate ring buffer - std::shared_ptr temp_ring_buffer = RingBuffer::create( + std::shared_ptr temp_ring_buffer = ring_buffer::RingBuffer::create( this_mww->microphone_source_->get_audio_stream_info().ms_to_bytes(RING_BUFFER_DURATION_MS)); if (temp_ring_buffer.use_count() == 0) { xEventGroupSetBits(this_mww->event_group_, EventGroupBits::ERROR_MEMORY); diff --git a/esphome/components/micro_wake_word/micro_wake_word.h b/esphome/components/micro_wake_word/micro_wake_word.h index 79a1226fba..5c0c056ac0 100644 --- a/esphome/components/micro_wake_word/micro_wake_word.h +++ b/esphome/components/micro_wake_word/micro_wake_word.h @@ -6,11 +6,11 @@ #include "streaming_model.h" #include "esphome/components/microphone/microphone_source.h" +#include "esphome/components/ring_buffer/ring_buffer.h" #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/defines.h" -#include "esphome/core/ring_buffer.h" #ifdef USE_OTA_STATE_LISTENER #include "esphome/components/ota/ota_backend.h" @@ -80,7 +80,7 @@ class MicroWakeWord : public Component Trigger wake_word_detected_trigger_; State state_{State::STOPPED}; - std::weak_ptr ring_buffer_; + std::weak_ptr ring_buffer_; std::vector wake_word_models_; #ifdef USE_MICRO_WAKE_WORD_VAD diff --git a/esphome/components/mixer/speaker/mixer_speaker.cpp b/esphome/components/mixer/speaker/mixer_speaker.cpp index 0d16bce330..8dea4560c2 100644 --- a/esphome/components/mixer/speaker/mixer_speaker.cpp +++ b/esphome/components/mixer/speaker/mixer_speaker.cpp @@ -226,7 +226,7 @@ size_t SourceSpeaker::play(const uint8_t *data, size_t length, TickType_t ticks_ this->start(); } size_t bytes_written = 0; - std::shared_ptr temp_ring_buffer = this->ring_buffer_.lock(); + std::shared_ptr temp_ring_buffer = this->ring_buffer_.lock(); if (temp_ring_buffer.use_count() > 0) { // Only write to the ring buffer if the reference is valid bytes_written = temp_ring_buffer->write_without_replacement(data, length, ticks_to_wait); @@ -263,9 +263,9 @@ esp_err_t SourceSpeaker::start_() { return ESP_ERR_NO_MEM; } - std::shared_ptr temp_ring_buffer = this->ring_buffer_.lock(); + std::shared_ptr temp_ring_buffer = this->ring_buffer_.lock(); if (!temp_ring_buffer) { - temp_ring_buffer = RingBuffer::create(ring_buffer_size); + temp_ring_buffer = ring_buffer::RingBuffer::create(ring_buffer_size); this->ring_buffer_ = temp_ring_buffer; } diff --git a/esphome/components/mixer/speaker/mixer_speaker.h b/esphome/components/mixer/speaker/mixer_speaker.h index 29876ea262..f392e83081 100644 --- a/esphome/components/mixer/speaker/mixer_speaker.h +++ b/esphome/components/mixer/speaker/mixer_speaker.h @@ -4,6 +4,7 @@ #include "esphome/components/audio/audio.h" #include "esphome/components/audio/audio_transfer_buffer.h" +#include "esphome/components/ring_buffer/ring_buffer.h" #include "esphome/components/speaker/speaker.h" #include "esphome/core/component.h" @@ -106,7 +107,7 @@ class SourceSpeaker : public speaker::Speaker, public Component { MixerSpeaker *parent_; std::shared_ptr transfer_buffer_; - std::weak_ptr ring_buffer_; + std::weak_ptr ring_buffer_; uint32_t buffer_duration_ms_; uint32_t last_seen_data_ms_{0}; diff --git a/esphome/components/resampler/speaker/resampler_speaker.cpp b/esphome/components/resampler/speaker/resampler_speaker.cpp index 9f69a7b50b..ecbd445a80 100644 --- a/esphome/components/resampler/speaker/resampler_speaker.cpp +++ b/esphome/components/resampler/speaker/resampler_speaker.cpp @@ -226,7 +226,7 @@ size_t ResamplerSpeaker::play(const uint8_t *data, size_t length, TickType_t tic if ((this->output_speaker_->is_running()) && (!this->requires_resampling_())) { bytes_written = this->output_speaker_->play(data, length, ticks_to_wait); } else { - std::shared_ptr temp_ring_buffer = this->ring_buffer_.lock(); + std::shared_ptr temp_ring_buffer = this->ring_buffer_.lock(); if (temp_ring_buffer) { // Only write to the ring buffer if the reference is valid bytes_written = temp_ring_buffer->write_without_replacement(data, length, ticks_to_wait); @@ -286,7 +286,7 @@ void ResamplerSpeaker::finish() { this->send_command_(ResamplingEventGroupBits:: bool ResamplerSpeaker::has_buffered_data() const { bool has_ring_buffer_data = false; if (this->requires_resampling_()) { - std::shared_ptr temp_ring_buffer = this->ring_buffer_.lock(); + std::shared_ptr temp_ring_buffer = this->ring_buffer_.lock(); if (temp_ring_buffer) { has_ring_buffer_data = (temp_ring_buffer->available() > 0); } @@ -323,8 +323,8 @@ void ResamplerSpeaker::resample_task(void *params) { this_resampler->taps_, this_resampler->filters_); if (err == ESP_OK) { - std::shared_ptr temp_ring_buffer = - RingBuffer::create(this_resampler->audio_stream_info_.ms_to_bytes(this_resampler->buffer_duration_ms_)); + std::shared_ptr temp_ring_buffer = ring_buffer::RingBuffer::create( + this_resampler->audio_stream_info_.ms_to_bytes(this_resampler->buffer_duration_ms_)); if (!temp_ring_buffer) { err = ESP_ERR_NO_MEM; diff --git a/esphome/components/resampler/speaker/resampler_speaker.h b/esphome/components/resampler/speaker/resampler_speaker.h index 36f39fda97..4a091e298a 100644 --- a/esphome/components/resampler/speaker/resampler_speaker.h +++ b/esphome/components/resampler/speaker/resampler_speaker.h @@ -4,6 +4,7 @@ #include "esphome/components/audio/audio.h" #include "esphome/components/audio/audio_transfer_buffer.h" +#include "esphome/components/ring_buffer/ring_buffer.h" #include "esphome/components/speaker/speaker.h" #include "esphome/core/component.h" @@ -75,7 +76,7 @@ class ResamplerSpeaker : public Component, public speaker::Speaker { EventGroupHandle_t event_group_{nullptr}; - std::weak_ptr ring_buffer_; + std::weak_ptr ring_buffer_; speaker::Speaker *output_speaker_{nullptr}; diff --git a/esphome/components/ring_buffer/__init__.py b/esphome/components/ring_buffer/__init__.py new file mode 100644 index 0000000000..b53476dcac --- /dev/null +++ b/esphome/components/ring_buffer/__init__.py @@ -0,0 +1,7 @@ +import esphome.codegen as cg + +CODEOWNERS = ["@kahrendt"] +DEPENDENCIES = ["esp32"] + +ring_buffer_ns = cg.esphome_ns.namespace("ring_buffer") +RingBuffer = ring_buffer_ns.class_("RingBuffer") diff --git a/esphome/core/ring_buffer.cpp b/esphome/components/ring_buffer/ring_buffer.cpp similarity index 97% rename from esphome/core/ring_buffer.cpp rename to esphome/components/ring_buffer/ring_buffer.cpp index 486cf67f25..9604290cf0 100644 --- a/esphome/core/ring_buffer.cpp +++ b/esphome/components/ring_buffer/ring_buffer.cpp @@ -5,7 +5,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -namespace esphome { +namespace esphome::ring_buffer { static const char *const TAG = "ring_buffer"; @@ -135,6 +135,6 @@ bool RingBuffer::discard_bytes_(size_t discard_bytes) { return (bytes_read == discard_bytes); } -} // namespace esphome +} // namespace esphome::ring_buffer -#endif +#endif // USE_ESP32 diff --git a/esphome/components/ring_buffer/ring_buffer.h b/esphome/components/ring_buffer/ring_buffer.h new file mode 100644 index 0000000000..62094899d7 --- /dev/null +++ b/esphome/components/ring_buffer/ring_buffer.h @@ -0,0 +1,126 @@ +#pragma once + +#ifdef USE_ESP32 + +#include +#include + +#include +#include + +namespace esphome::ring_buffer { + +class RingBuffer { + public: + ~RingBuffer(); + + /** + * @brief Reads from the ring buffer, waiting up to a specified number of ticks if necessary. + * + * Available bytes are read into the provided data pointer. If not enough bytes are available, + * the function will wait up to `ticks_to_wait` FreeRTOS ticks before reading what is available. + * + * @param data Pointer to copy read data into + * @param len Number of bytes to read + * @param ticks_to_wait Maximum number of FreeRTOS ticks to wait (default: 0) + * @return Number of bytes read + */ + size_t read(void *data, size_t len, TickType_t ticks_to_wait = 0); + + /** + * @brief Acquires a pointer into the ring buffer's internal storage without copying. + * + * The returned pointer is valid until receive_release() is called. Only one item + * may be checked out at a time. + * + * @param[out] length Set to the number of bytes actually acquired (may be less than max_length at wrap boundary) + * @param max_length Maximum number of bytes to acquire + * @param ticks_to_wait Maximum number of FreeRTOS ticks to wait (default: 0) + * @return Pointer into the ring buffer's internal storage, or nullptr if no data is available + */ + void *receive_acquire(size_t &length, size_t max_length, TickType_t ticks_to_wait = 0); + + /** + * @brief Releases a previously acquired ring buffer item. + * + * Must be called exactly once for each successful receive_acquire(). + * + * @param item Pointer returned by receive_acquire() + */ + void receive_release(void *item); + + /** + * @brief Writes to the ring buffer, overwriting oldest data if necessary. + * + * The provided data is written to the ring buffer. If not enough space is available, + * the function will overwrite the oldest data in the ring buffer. + * + * @param data Pointer to data for writing + * @param len Number of bytes to write + * @return Number of bytes written + */ + size_t write(const void *data, size_t len); + + /** + * @brief Writes to the ring buffer without overwriting oldest data. + * + * The provided data is written to the ring buffer. If not enough space is available, + * the function will wait up to `ticks_to_wait` FreeRTOS ticks before writing as much as possible. + * + * @param data Pointer to data for writing + * @param len Number of bytes to write + * @param ticks_to_wait Maximum number of FreeRTOS ticks to wait (default: 0) + * @return Number of bytes written + */ + size_t write_without_replacement(const void *data, size_t len, TickType_t ticks_to_wait = 0, + bool write_partial = true); + + /** + * @brief Returns the number of available bytes in the ring buffer. + * + * This function provides the number of bytes that can be read from the ring buffer + * without blocking the calling FreeRTOS task. + * + * @return Number of available bytes + */ + size_t available() const; + + /** + * @brief Returns the number of free bytes in the ring buffer. + * + * This function provides the number of bytes that can be written to the ring buffer + * without overwriting data or blocking the calling FreeRTOS task. + * + * @return Number of free bytes + */ + size_t free() const; + + /** + * @brief Resets the ring buffer, discarding all stored data. + * + * @return pdPASS if successful, pdFAIL otherwise + */ + BaseType_t reset(); + + enum class MemoryPreference { + EXTERNAL_FIRST, // External RAM preferred, fall back to internal (default) + INTERNAL_FIRST, // Internal RAM preferred, fall back to external + }; + + static std::unique_ptr create(size_t len, MemoryPreference preference = MemoryPreference::EXTERNAL_FIRST); + + protected: + /// @brief Discards data from the ring buffer. + /// @param discard_bytes amount of bytes to discard + /// @return True if all bytes were successfully discarded, false otherwise + bool discard_bytes_(size_t discard_bytes); + + RingbufHandle_t handle_{nullptr}; + StaticRingbuffer_t structure_; + uint8_t *storage_{nullptr}; + size_t size_{0}; +}; + +} // namespace esphome::ring_buffer + +#endif // USE_ESP32 diff --git a/esphome/components/sound_level/sound_level.cpp b/esphome/components/sound_level/sound_level.cpp index 99533dbdd5..fb8bfd3085 100644 --- a/esphome/components/sound_level/sound_level.cpp +++ b/esphome/components/sound_level/sound_level.cpp @@ -29,7 +29,7 @@ void SoundLevelComponent::dump_config() { void SoundLevelComponent::setup() { this->microphone_source_->add_data_callback([this](const std::vector &data) { - std::shared_ptr temp_ring_buffer = this->ring_buffer_.lock(); + std::shared_ptr temp_ring_buffer = this->ring_buffer_.lock(); if (this->ring_buffer_.use_count() == 2) { // ``audio_buffer_`` and ``temp_ring_buffer`` share ownership of a ring buffer, so its safe/useful to write temp_ring_buffer->write((void *) data.data(), data.size()); @@ -172,8 +172,8 @@ bool SoundLevelComponent::start_() { // Allocates a new ring buffer, adds it as a source for the transfer buffer, and points ring_buffer_ to it this->ring_buffer_.reset(); // Reset pointer to any previous ring buffer allocation - std::shared_ptr temp_ring_buffer = - RingBuffer::create(this->microphone_source_->get_audio_stream_info().ms_to_bytes(RING_BUFFER_DURATION_MS)); + std::shared_ptr temp_ring_buffer = ring_buffer::RingBuffer::create( + this->microphone_source_->get_audio_stream_info().ms_to_bytes(RING_BUFFER_DURATION_MS)); if (temp_ring_buffer.use_count() == 0) { this->status_momentary_error("ring_buffer", 15000); this->stop_(); diff --git a/esphome/components/sound_level/sound_level.h b/esphome/components/sound_level/sound_level.h index 0e46a203e8..4f0081a510 100644 --- a/esphome/components/sound_level/sound_level.h +++ b/esphome/components/sound_level/sound_level.h @@ -4,11 +4,11 @@ #include "esphome/components/audio/audio_transfer_buffer.h" #include "esphome/components/microphone/microphone_source.h" +#include "esphome/components/ring_buffer/ring_buffer.h" #include "esphome/components/sensor/sensor.h" #include "esphome/core/automation.h" #include "esphome/core/component.h" -#include "esphome/core/ring_buffer.h" namespace esphome::sound_level { @@ -49,7 +49,7 @@ class SoundLevelComponent : public Component { sensor::Sensor *rms_sensor_{nullptr}; std::unique_ptr audio_buffer_; - std::weak_ptr ring_buffer_; + std::weak_ptr ring_buffer_; int32_t squared_peak_{0}; uint64_t squared_samples_sum_{0}; diff --git a/esphome/components/speaker/media_player/audio_pipeline.cpp b/esphome/components/speaker/media_player/audio_pipeline.cpp index 892d4f4112..010f0c50b3 100644 --- a/esphome/components/speaker/media_player/audio_pipeline.cpp +++ b/esphome/components/speaker/media_player/audio_pipeline.cpp @@ -315,10 +315,10 @@ void AudioPipeline::read_task(void *params) { if (err == ESP_OK) { size_t file_ring_buffer_size = this_pipeline->buffer_size_; - std::shared_ptr temp_ring_buffer; + std::shared_ptr temp_ring_buffer; if (!this_pipeline->raw_file_ring_buffer_.use_count()) { - temp_ring_buffer = RingBuffer::create(file_ring_buffer_size); + temp_ring_buffer = ring_buffer::RingBuffer::create(file_ring_buffer_size); this_pipeline->raw_file_ring_buffer_ = temp_ring_buffer; } @@ -502,7 +502,7 @@ void AudioPipeline::decode_task(void *params) { if (!started_playback && has_stream_info) { // Verify enough data is available before starting playback - std::shared_ptr temp_ring_buffer = this_pipeline->raw_file_ring_buffer_.lock(); + std::shared_ptr temp_ring_buffer = this_pipeline->raw_file_ring_buffer_.lock(); if (temp_ring_buffer != nullptr && temp_ring_buffer->available() >= initial_bytes_to_buffer) { started_playback = true; } diff --git a/esphome/components/speaker/media_player/audio_pipeline.h b/esphome/components/speaker/media_player/audio_pipeline.h index ef7f75dd38..89f4707ab3 100644 --- a/esphome/components/speaker/media_player/audio_pipeline.h +++ b/esphome/components/speaker/media_player/audio_pipeline.h @@ -5,9 +5,9 @@ #include "esphome/components/audio/audio.h" #include "esphome/components/audio/audio_reader.h" #include "esphome/components/audio/audio_decoder.h" +#include "esphome/components/ring_buffer/ring_buffer.h" #include "esphome/components/speaker/speaker.h" -#include "esphome/core/ring_buffer.h" #include "esphome/core/static_task.h" #include "esp_err.h" @@ -129,7 +129,7 @@ class AudioPipeline { size_t buffer_size_; // Ring buffer between reader and decoder size_t transfer_buffer_size_; // Internal source/sink buffers for the audio reader and decoder - std::weak_ptr raw_file_ring_buffer_; + std::weak_ptr raw_file_ring_buffer_; // Handles basic control/state of the three tasks EventGroupHandle_t event_group_{nullptr}; diff --git a/esphome/components/voice_assistant/__init__.py b/esphome/components/voice_assistant/__init__.py index d970df2a44..9387797ba2 100644 --- a/esphome/components/voice_assistant/__init__.py +++ b/esphome/components/voice_assistant/__init__.py @@ -15,7 +15,7 @@ from esphome.const import ( CONF_SPEAKER, ) -AUTO_LOAD = ["socket"] +AUTO_LOAD = ["ring_buffer", "socket"] DEPENDENCIES = ["api", "microphone"] CODEOWNERS = ["@jesserockz", "@kahrendt"] diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index f2244a8ff4..bff0026b24 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -30,7 +30,7 @@ VoiceAssistant::VoiceAssistant() { global_voice_assistant = this; } void VoiceAssistant::setup() { this->mic_source_->add_data_callback([this](const std::vector &data) { - std::shared_ptr temp_ring_buffer = this->ring_buffer_; + std::shared_ptr temp_ring_buffer = this->ring_buffer_; if (this->ring_buffer_.use_count() > 1) { temp_ring_buffer->write((void *) data.data(), data.size()); } @@ -116,7 +116,7 @@ bool VoiceAssistant::allocate_buffers_() { #endif if (this->ring_buffer_.use_count() == 0) { - this->ring_buffer_ = RingBuffer::create(RING_BUFFER_SIZE); + this->ring_buffer_ = ring_buffer::RingBuffer::create(RING_BUFFER_SIZE); if (this->ring_buffer_.use_count() == 0) { ESP_LOGE(TAG, "Could not allocate ring buffer"); return false; diff --git a/esphome/components/voice_assistant/voice_assistant.h b/esphome/components/voice_assistant/voice_assistant.h index eb23dcb5e0..faef09d8bd 100644 --- a/esphome/components/voice_assistant/voice_assistant.h +++ b/esphome/components/voice_assistant/voice_assistant.h @@ -7,9 +7,9 @@ #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" -#include "esphome/core/ring_buffer.h" #include "esphome/components/api/api_connection.h" +#include "esphome/components/ring_buffer/ring_buffer.h" #include "esphome/components/api/api_pb2.h" #include "esphome/components/microphone/microphone_source.h" #ifdef USE_MEDIA_PLAYER @@ -300,7 +300,7 @@ class VoiceAssistant : public Component { std::string wake_word_{""}; - std::shared_ptr ring_buffer_; + std::shared_ptr ring_buffer_; bool use_wake_word_; uint8_t noise_suppression_level_; diff --git a/esphome/core/config.py b/esphome/core/config.py index fe55c0fe25..5a98b94781 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -759,10 +759,6 @@ async def to_code(config: ConfigType) -> None: # Platform-specific source files for core FILTER_SOURCE_FILES = filter_source_files_from_platform( { - "ring_buffer.cpp": { - PlatformFramework.ESP32_ARDUINO, - PlatformFramework.ESP32_IDF, - }, "static_task.cpp": { PlatformFramework.ESP32_ARDUINO, PlatformFramework.ESP32_IDF, diff --git a/esphome/core/ring_buffer.h b/esphome/core/ring_buffer.h index 8ac3ff3811..fbb089e906 100644 --- a/esphome/core/ring_buffer.h +++ b/esphome/core/ring_buffer.h @@ -2,124 +2,20 @@ #ifdef USE_ESP32 -#include -#include - -#include -#include +// Deprecated: include "esphome/components/ring_buffer/ring_buffer.h" and use +// esphome::ring_buffer::RingBuffer. This shim will be removed in 2026.11.0. +#if __has_include("esphome/components/ring_buffer/ring_buffer.h") +#include "esphome/components/ring_buffer/ring_buffer.h" +#else +#error \ + "esphome/components/ring_buffer/ring_buffer.h not found. Add 'ring_buffer' to your component's AUTO_LOAD list to use esphome::ring_buffer::RingBuffer." +#endif +#include "esphome/core/helpers.h" // for ESPDEPRECATED namespace esphome { -class RingBuffer { - public: - ~RingBuffer(); - - /** - * @brief Reads from the ring buffer, waiting up to a specified number of ticks if necessary. - * - * Available bytes are read into the provided data pointer. If not enough bytes are available, - * the function will wait up to `ticks_to_wait` FreeRTOS ticks before reading what is available. - * - * @param data Pointer to copy read data into - * @param len Number of bytes to read - * @param ticks_to_wait Maximum number of FreeRTOS ticks to wait (default: 0) - * @return Number of bytes read - */ - size_t read(void *data, size_t len, TickType_t ticks_to_wait = 0); - - /** - * @brief Acquires a pointer into the ring buffer's internal storage without copying. - * - * The returned pointer is valid until receive_release() is called. Only one item - * may be checked out at a time. - * - * @param[out] length Set to the number of bytes actually acquired (may be less than max_length at wrap boundary) - * @param max_length Maximum number of bytes to acquire - * @param ticks_to_wait Maximum number of FreeRTOS ticks to wait (default: 0) - * @return Pointer into the ring buffer's internal storage, or nullptr if no data is available - */ - void *receive_acquire(size_t &length, size_t max_length, TickType_t ticks_to_wait = 0); - - /** - * @brief Releases a previously acquired ring buffer item. - * - * Must be called exactly once for each successful receive_acquire(). - * - * @param item Pointer returned by receive_acquire() - */ - void receive_release(void *item); - - /** - * @brief Writes to the ring buffer, overwriting oldest data if necessary. - * - * The provided data is written to the ring buffer. If not enough space is available, - * the function will overwrite the oldest data in the ring buffer. - * - * @param data Pointer to data for writing - * @param len Number of bytes to write - * @return Number of bytes written - */ - size_t write(const void *data, size_t len); - - /** - * @brief Writes to the ring buffer without overwriting oldest data. - * - * The provided data is written to the ring buffer. If not enough space is available, - * the function will wait up to `ticks_to_wait` FreeRTOS ticks before writing as much as possible. - * - * @param data Pointer to data for writing - * @param len Number of bytes to write - * @param ticks_to_wait Maximum number of FreeRTOS ticks to wait (default: 0) - * @return Number of bytes written - */ - size_t write_without_replacement(const void *data, size_t len, TickType_t ticks_to_wait = 0, - bool write_partial = true); - - /** - * @brief Returns the number of available bytes in the ring buffer. - * - * This function provides the number of bytes that can be read from the ring buffer - * without blocking the calling FreeRTOS task. - * - * @return Number of available bytes - */ - size_t available() const; - - /** - * @brief Returns the number of free bytes in the ring buffer. - * - * This function provides the number of bytes that can be written to the ring buffer - * without overwriting data or blocking the calling FreeRTOS task. - * - * @return Number of free bytes - */ - size_t free() const; - - /** - * @brief Resets the ring buffer, discarding all stored data. - * - * @return pdPASS if successful, pdFAIL otherwise - */ - BaseType_t reset(); - - enum class MemoryPreference { - EXTERNAL_FIRST, // External RAM preferred, fall back to internal (default) - INTERNAL_FIRST, // Internal RAM preferred, fall back to external - }; - - static std::unique_ptr create(size_t len, MemoryPreference preference = MemoryPreference::EXTERNAL_FIRST); - - protected: - /// @brief Discards data from the ring buffer. - /// @param discard_bytes amount of bytes to discard - /// @return True if all bytes were successfully discarded, false otherwise - bool discard_bytes_(size_t discard_bytes); - - RingbufHandle_t handle_{nullptr}; - StaticRingbuffer_t structure_; - uint8_t *storage_{nullptr}; - size_t size_{0}; -}; +using RingBuffer ESPDEPRECATED("Use esphome::ring_buffer::RingBuffer instead. Removed in 2026.11.0.", + "2026.5.0") = ring_buffer::RingBuffer; } // namespace esphome diff --git a/esphome/writer.py b/esphome/writer.py index 816c57a0bc..2fa43fa5eb 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -199,7 +199,15 @@ def copy_src_tree(): # Build #include list for esphome.h # X-macro files are included multiple times with different macro definitions # and must not be included bare in esphome.h - esphome_h_exclude = {Path(ENTITY_TYPES_H_TARGET)} + # Deprecated headers that re-export from a relocated component must not be + # auto-included, since their #include of the new path only resolves when the + # new component is loaded by a consumer. + esphome_h_exclude = { + Path(ENTITY_TYPES_H_TARGET), + Path( + "esphome/core/ring_buffer.h" + ), # moved to components/ring_buffer/, removed in 2026.11.0 + } include_l = [] for target, _ in source_files_l: if target.suffix in HEADER_FILE_EXTENSIONS and target not in esphome_h_exclude: