[core] Move core ring buffer to helper component (#16298)

This commit is contained in:
Kevin Ahrendt
2026-05-07 22:01:37 -04:00
committed by GitHub
parent e152c6155b
commit 08b17c9da1
32 changed files with 214 additions and 174 deletions
+1
View File
@@ -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
+1
View File
@@ -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")
+2 -2
View File
@@ -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<RingBuffer> &input_ring_buffer) {
esp_err_t AudioDecoder::add_source(std::weak_ptr<ring_buffer::RingBuffer> &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<RingBuffer> &output_ring_buffer) {
esp_err_t AudioDecoder::add_sink(std::weak_ptr<ring_buffer::RingBuffer> &output_ring_buffer) {
if (this->output_transfer_buffer_ != nullptr) {
this->output_transfer_buffer_->set_sink(output_ring_buffer);
return ESP_OK;
+3 -3
View File
@@ -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<RingBuffer> &input_ring_buffer);
esp_err_t add_source(std::weak_ptr<ring_buffer::RingBuffer> &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<RingBuffer> &output_ring_buffer);
esp_err_t add_sink(std::weak_ptr<ring_buffer::RingBuffer> &output_ring_buffer);
#ifdef USE_SPEAKER
/// @brief Adds a sink speaker for decoded audio.
+1 -1
View File
@@ -54,7 +54,7 @@ enum HttpStatus {
AudioReader::~AudioReader() { this->cleanup_connection_(); }
esp_err_t AudioReader::add_sink(const std::weak_ptr<RingBuffer> &output_ring_buffer) {
esp_err_t AudioReader::add_sink(const std::weak_ptr<ring_buffer::RingBuffer> &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();
+3 -3
View File
@@ -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<RingBuffer> &output_ring_buffer);
esp_err_t add_sink(const std::weak_ptr<ring_buffer::RingBuffer> &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<RingBuffer> file_ring_buffer_;
std::shared_ptr<ring_buffer::RingBuffer> file_ring_buffer_;
std::unique_ptr<AudioSinkTransferBuffer> output_transfer_buffer_;
void cleanup_connection_();
+2 -2
View File
@@ -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<RingBuffer> &input_ring_buffer) {
esp_err_t AudioResampler::add_source(std::weak_ptr<ring_buffer::RingBuffer> &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<RingBuffer> &input_ring_buffe
return ESP_ERR_NO_MEM;
}
esp_err_t AudioResampler::add_sink(std::weak_ptr<RingBuffer> &output_ring_buffer) {
esp_err_t AudioResampler::add_sink(std::weak_ptr<ring_buffer::RingBuffer> &output_ring_buffer) {
if (this->output_transfer_buffer_ != nullptr) {
this->output_transfer_buffer_->set_sink(output_ring_buffer);
return ESP_OK;
+3 -3
View File
@@ -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<RingBuffer> &input_ring_buffer);
esp_err_t add_source(std::weak_ptr<ring_buffer::RingBuffer> &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<RingBuffer> &output_ring_buffer);
esp_err_t add_sink(std::weak_ptr<ring_buffer::RingBuffer> &output_ring_buffer);
#ifdef USE_SPEAKER
/// @brief Adds a sink speaker for decoded audio.
@@ -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<RingBuffer> ring_buffer_;
std::shared_ptr<ring_buffer::RingBuffer> 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<RingBuffer> &ring_buffer) { this->ring_buffer_ = ring_buffer.lock(); }
void set_sink(const std::weak_ptr<ring_buffer::RingBuffer> &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<RingBuffer> &ring_buffer) { this->ring_buffer_ = ring_buffer.lock(); };
void set_source(const std::weak_ptr<ring_buffer::RingBuffer> &ring_buffer) {
this->ring_buffer_ = ring_buffer.lock();
};
// AudioReadableBuffer interface
const uint8_t *data() const override { return this->data_start_; }
@@ -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<RingBuffer> temp_ring_buffer = this->audio_ring_buffer_.lock();
std::shared_ptr<ring_buffer::RingBuffer> 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<RingBuffer> temp_ring_buffer = this->audio_ring_buffer_.lock();
std::shared_ptr<ring_buffer::RingBuffer> temp_ring_buffer = this->audio_ring_buffer_.lock();
return temp_ring_buffer->available() > 0;
}
return false;
@@ -9,12 +9,12 @@
#include <freertos/FreeRTOS.h>
#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<RingBuffer> audio_ring_buffer_;
std::weak_ptr<ring_buffer::RingBuffer> audio_ring_buffer_;
uint32_t buffer_duration_ms_;
@@ -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<RingBuffer> temp_ring_buffer = RingBuffer::create(ring_buffer_size);
std::shared_ptr<ring_buffer::RingBuffer> 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;
@@ -30,6 +30,7 @@ from esphome.core import CORE, HexInt
_LOGGER = logging.getLogger(__name__)
AUTO_LOAD = ["ring_buffer"]
CODEOWNERS = ["@kahrendt", "@jesserockz"]
DEPENDENCIES = ["microphone"]
@@ -106,7 +106,7 @@ void MicroWakeWord::setup() {
if (this->state_ == State::STOPPED) {
return;
}
std::shared_ptr<RingBuffer> temp_ring_buffer = this->ring_buffer_.lock();
std::shared_ptr<ring_buffer::RingBuffer> 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<RingBuffer> temp_ring_buffer = RingBuffer::create(
std::shared_ptr<ring_buffer::RingBuffer> 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);
@@ -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<std::string> wake_word_detected_trigger_;
State state_{State::STOPPED};
std::weak_ptr<RingBuffer> ring_buffer_;
std::weak_ptr<ring_buffer::RingBuffer> ring_buffer_;
std::vector<WakeWordModel *> wake_word_models_;
#ifdef USE_MICRO_WAKE_WORD_VAD
@@ -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<RingBuffer> temp_ring_buffer = this->ring_buffer_.lock();
std::shared_ptr<ring_buffer::RingBuffer> 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<RingBuffer> temp_ring_buffer = this->ring_buffer_.lock();
std::shared_ptr<ring_buffer::RingBuffer> 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;
}
@@ -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<audio::AudioSourceTransferBuffer> transfer_buffer_;
std::weak_ptr<RingBuffer> ring_buffer_;
std::weak_ptr<ring_buffer::RingBuffer> ring_buffer_;
uint32_t buffer_duration_ms_;
uint32_t last_seen_data_ms_{0};
@@ -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<RingBuffer> temp_ring_buffer = this->ring_buffer_.lock();
std::shared_ptr<ring_buffer::RingBuffer> 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<RingBuffer> temp_ring_buffer = this->ring_buffer_.lock();
std::shared_ptr<ring_buffer::RingBuffer> 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<RingBuffer> temp_ring_buffer =
RingBuffer::create(this_resampler->audio_stream_info_.ms_to_bytes(this_resampler->buffer_duration_ms_));
std::shared_ptr<ring_buffer::RingBuffer> 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;
@@ -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<RingBuffer> ring_buffer_;
std::weak_ptr<ring_buffer::RingBuffer> ring_buffer_;
speaker::Speaker *output_speaker_{nullptr};
@@ -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")
@@ -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
@@ -0,0 +1,126 @@
#pragma once
#ifdef USE_ESP32
#include <freertos/FreeRTOS.h>
#include <freertos/ringbuf.h>
#include <cinttypes>
#include <memory>
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<RingBuffer> 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
@@ -29,7 +29,7 @@ void SoundLevelComponent::dump_config() {
void SoundLevelComponent::setup() {
this->microphone_source_->add_data_callback([this](const std::vector<uint8_t> &data) {
std::shared_ptr<RingBuffer> temp_ring_buffer = this->ring_buffer_.lock();
std::shared_ptr<ring_buffer::RingBuffer> 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<RingBuffer> temp_ring_buffer =
RingBuffer::create(this->microphone_source_->get_audio_stream_info().ms_to_bytes(RING_BUFFER_DURATION_MS));
std::shared_ptr<ring_buffer::RingBuffer> 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_();
+2 -2
View File
@@ -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::AudioSourceTransferBuffer> audio_buffer_;
std::weak_ptr<RingBuffer> ring_buffer_;
std::weak_ptr<ring_buffer::RingBuffer> ring_buffer_;
int32_t squared_peak_{0};
uint64_t squared_samples_sum_{0};
@@ -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<RingBuffer> temp_ring_buffer;
std::shared_ptr<ring_buffer::RingBuffer> 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<RingBuffer> temp_ring_buffer = this_pipeline->raw_file_ring_buffer_.lock();
std::shared_ptr<ring_buffer::RingBuffer> 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;
}
@@ -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<RingBuffer> raw_file_ring_buffer_;
std::weak_ptr<ring_buffer::RingBuffer> raw_file_ring_buffer_;
// Handles basic control/state of the three tasks
EventGroupHandle_t event_group_{nullptr};
@@ -15,7 +15,7 @@ from esphome.const import (
CONF_SPEAKER,
)
AUTO_LOAD = ["socket"]
AUTO_LOAD = ["ring_buffer", "socket"]
DEPENDENCIES = ["api", "microphone"]
CODEOWNERS = ["@jesserockz", "@kahrendt"]
@@ -30,7 +30,7 @@ VoiceAssistant::VoiceAssistant() { global_voice_assistant = this; }
void VoiceAssistant::setup() {
this->mic_source_->add_data_callback([this](const std::vector<uint8_t> &data) {
std::shared_ptr<RingBuffer> temp_ring_buffer = this->ring_buffer_;
std::shared_ptr<ring_buffer::RingBuffer> 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;
@@ -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<RingBuffer> ring_buffer_;
std::shared_ptr<ring_buffer::RingBuffer> ring_buffer_;
bool use_wake_word_;
uint8_t noise_suppression_level_;
-4
View File
@@ -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,
+11 -115
View File
@@ -2,124 +2,20 @@
#ifdef USE_ESP32
#include <freertos/FreeRTOS.h>
#include <freertos/ringbuf.h>
#include <cinttypes>
#include <memory>
// 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<RingBuffer> 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
+9 -1
View File
@@ -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: