[sound_level] Use RingBufferAudioSource (#16436)

This commit is contained in:
Kevin Ahrendt
2026-05-14 23:33:36 -04:00
committed by GitHub
parent c5c627d534
commit d663d80fde
2 changed files with 35 additions and 32 deletions
+30 -28
View File
@@ -11,7 +11,7 @@ namespace esphome::sound_level {
static const char *const TAG = "sound_level"; static const char *const TAG = "sound_level";
static const uint32_t AUDIO_BUFFER_DURATION_MS = 30; static const uint32_t MAX_FILL_DURATION_MS = 30;
static const uint32_t RING_BUFFER_DURATION_MS = 120; static const uint32_t RING_BUFFER_DURATION_MS = 120;
// Square INT16_MIN since INT16_MIN^2 > INT16_MAX^2 // Square INT16_MIN since INT16_MIN^2 > INT16_MAX^2
@@ -30,8 +30,7 @@ void SoundLevelComponent::dump_config() {
void SoundLevelComponent::setup() { void SoundLevelComponent::setup() {
this->microphone_source_->add_data_callback([this](const std::vector<uint8_t> &data) { this->microphone_source_->add_data_callback([this](const std::vector<uint8_t> &data) {
std::shared_ptr<ring_buffer::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) { if (temp_ring_buffer != nullptr) {
// ``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()); temp_ring_buffer->write((void *) data.data(), data.size());
} }
}); });
@@ -81,10 +80,11 @@ void SoundLevelComponent::loop() {
return; return;
} }
// Copy data from ring buffer into the transfer buffer - don't block to avoid slowing the main loop // Expose a chunk of the ring buffer's internal storage - don't block to avoid slowing the main loop.
this->audio_buffer_->transfer_data_from_source(0); // pre_shift is ignored by RingBufferAudioSource (no intermediate transfer buffer to compact).
this->audio_source_->fill(0, false);
if (this->audio_buffer_->available() == 0) { if (this->audio_source_->available() == 0) {
// No new audio available for processing // No new audio available for processing
return; return;
} }
@@ -92,11 +92,11 @@ void SoundLevelComponent::loop() {
const uint32_t samples_in_window = const uint32_t samples_in_window =
this->microphone_source_->get_audio_stream_info().ms_to_samples(this->measurement_duration_ms_); this->microphone_source_->get_audio_stream_info().ms_to_samples(this->measurement_duration_ms_);
const uint32_t samples_available_to_process = const uint32_t samples_available_to_process =
this->microphone_source_->get_audio_stream_info().bytes_to_samples(this->audio_buffer_->available()); this->microphone_source_->get_audio_stream_info().bytes_to_samples(this->audio_source_->available());
const uint32_t samples_to_process = std::min(samples_in_window - this->sample_count_, samples_available_to_process); const uint32_t samples_to_process = std::min(samples_in_window - this->sample_count_, samples_available_to_process);
// MicrophoneSource always provides int16 samples due to Python codegen settings // MicrophoneSource always provides int16 samples due to Python codegen settings
const int16_t *audio_data = reinterpret_cast<const int16_t *>(this->audio_buffer_->get_buffer_start()); const int16_t *audio_data = reinterpret_cast<const int16_t *>(this->audio_source_->data());
// Process all the new audio samples // Process all the new audio samples
for (uint32_t i = 0; i < samples_to_process; ++i) { for (uint32_t i = 0; i < samples_to_process; ++i) {
@@ -115,9 +115,8 @@ void SoundLevelComponent::loop() {
++this->sample_count_; ++this->sample_count_;
} }
// Remove the processed samples from ``audio_buffer_`` // Remove the processed samples from ``audio_source_``
this->audio_buffer_->decrease_buffer_length( this->audio_source_->consume(this->microphone_source_->get_audio_stream_info().samples_to_bytes(samples_to_process));
this->microphone_source_->get_audio_stream_info().samples_to_bytes(samples_to_process));
if (this->sample_count_ == samples_in_window) { if (this->sample_count_ == samples_in_window) {
// Processed enough samples for the measurement window, compute and publish the sensor values // Processed enough samples for the measurement window, compute and publish the sensor values
@@ -158,36 +157,39 @@ void SoundLevelComponent::stop() {
} }
bool SoundLevelComponent::start_() { bool SoundLevelComponent::start_() {
if (this->audio_buffer_ != nullptr) { if (this->audio_source_ != nullptr) {
return true; return true;
} }
// Allocate a transfer buffer const auto &stream_info = this->microphone_source_->get_audio_stream_info();
this->audio_buffer_ = audio::AudioSourceTransferBuffer::create( const size_t bytes_per_frame = stream_info.frames_to_bytes(1);
this->microphone_source_->get_audio_stream_info().ms_to_bytes(AUDIO_BUFFER_DURATION_MS));
if (this->audio_buffer_ == nullptr) { // Allocate a ring buffer for the microphone callback to write into. Round the size down to a multiple
this->status_momentary_error("transfer_buffer", 15000); // of bytes_per_frame so the wrap boundary stays frame-aligned and avoids unnecessary single-frame splices.
this->ring_buffer_.reset(); // Reset pointer to any previous ring buffer allocation
const size_t ring_buffer_size =
(stream_info.ms_to_bytes(RING_BUFFER_DURATION_MS) / bytes_per_frame) * bytes_per_frame;
std::shared_ptr<ring_buffer::RingBuffer> temp_ring_buffer = ring_buffer::RingBuffer::create(ring_buffer_size);
if (temp_ring_buffer == nullptr) {
this->status_momentary_error("ring_buffer", 15000);
return false; return false;
} }
// Allocates a new ring buffer, adds it as a source for the transfer buffer, and points ring_buffer_ to it // Zero-copy source that reads directly from the ring buffer's internal storage. Frame-aligned reads
this->ring_buffer_.reset(); // Reset pointer to any previous ring buffer allocation // ensure multi-channel frames are never split across the ring buffer's wrap boundary.
std::shared_ptr<ring_buffer::RingBuffer> temp_ring_buffer = ring_buffer::RingBuffer::create( this->audio_source_ = audio::RingBufferAudioSource::create(
this->microphone_source_->get_audio_stream_info().ms_to_bytes(RING_BUFFER_DURATION_MS)); temp_ring_buffer, stream_info.ms_to_bytes(MAX_FILL_DURATION_MS), static_cast<uint8_t>(bytes_per_frame));
if (temp_ring_buffer.use_count() == 0) { if (this->audio_source_ == nullptr) {
this->status_momentary_error("ring_buffer", 15000); this->status_momentary_error("audio_source", 15000);
this->stop_();
return false; return false;
} else {
this->ring_buffer_ = temp_ring_buffer;
this->audio_buffer_->set_source(temp_ring_buffer);
} }
this->ring_buffer_ = temp_ring_buffer;
this->status_clear_error(); this->status_clear_error();
return true; return true;
} }
void SoundLevelComponent::stop_() { this->audio_buffer_.reset(); } void SoundLevelComponent::stop_() { this->audio_source_.reset(); }
} // namespace esphome::sound_level } // namespace esphome::sound_level
+5 -4
View File
@@ -36,11 +36,12 @@ class SoundLevelComponent : public Component {
void stop(); void stop();
protected: protected:
/// @brief Internal start command that, if necessary, allocates ``audio_buffer_`` and a ring buffer which /// @brief Internal start command that, if necessary, allocates a ring buffer and a zero-copy
/// ``audio_buffer_`` owns and ``ring_buffer_`` points to. Returns true if allocations were successful. /// ``RingBufferAudioSource`` that reads directly from it. ``ring_buffer_`` weakly references the
/// ring buffer owned by ``audio_source_``. Returns true if allocations were successful.
bool start_(); bool start_();
/// @brief Internal stop command the deallocates ``audio_buffer_`` (which automatically deallocates its ring buffer) /// @brief Internal stop command that deallocates ``audio_source_`` (which releases its ring buffer)
void stop_(); void stop_();
microphone::MicrophoneSource *microphone_source_{nullptr}; microphone::MicrophoneSource *microphone_source_{nullptr};
@@ -48,7 +49,7 @@ class SoundLevelComponent : public Component {
sensor::Sensor *peak_sensor_{nullptr}; sensor::Sensor *peak_sensor_{nullptr};
sensor::Sensor *rms_sensor_{nullptr}; sensor::Sensor *rms_sensor_{nullptr};
std::unique_ptr<audio::AudioSourceTransferBuffer> audio_buffer_; std::unique_ptr<audio::RingBufferAudioSource> audio_source_;
std::weak_ptr<ring_buffer::RingBuffer> ring_buffer_; std::weak_ptr<ring_buffer::RingBuffer> ring_buffer_;
int32_t squared_peak_{0}; int32_t squared_peak_{0};