ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
resampler_microphone.cpp
Go to the documentation of this file.
2
3#ifdef USE_ESP32
4
6#include "esphome/core/log.h"
7
8#include <algorithm>
9
11
12ESPHOME_LOG_TAG(TAG, "resampler.microphone");
13
14// Duration of audio the resampler converts per step; longer source chunks are processed in several steps
15static constexpr uint32_t BUFFER_DURATION_MS = 16;
16
18 const audio::AudioStreamInfo input_stream_info = this->source_->get_audio_stream_info();
20 input_stream_info.get_channels(), this->target_sample_rate_);
21
22 // Allocate now for the expected source format; process_audio_ only sets up again if that format changes
23 if (!this->init_resampler_(input_stream_info)) {
24 this->mark_failed();
25 return;
26 }
27
28 this->source_->add_data_callback([this](const std::vector<uint8_t> &data) { this->process_audio_(data); });
29
30 this->disable_loop();
31}
32
34 ESP_LOGCONFIG(TAG,
35 "Resampler Microphone:\n"
36 " Target Sample Rate: %" PRIu32 " Hz\n"
37 " Taps: %u\n"
38 " Filters: %u",
39 this->target_sample_rate_, this->taps_, this->filters_);
40}
41
43 if (this->is_failed() || this->active_listeners_ == UINT8_MAX)
44 return;
45 ++this->active_listeners_;
46 this->enable_loop();
47}
48
50 if (this->active_listeners_ == 0)
51 return;
52 --this->active_listeners_;
53 this->enable_loop();
54}
55
57 if (this->active_listeners_ == 0) {
58 if (this->state_ != microphone::STATE_STOPPED) {
59 this->source_->stop();
61 }
62 this->disable_loop();
63 return;
64 }
65
66 switch (this->state_) {
68 this->source_->start();
70 break;
72 if (this->source_->is_running()) {
74 }
75 break;
77 // Follow the source if it restarts, e.g. after a driver error
78 if (!this->source_->is_running()) {
80 }
81 break;
83 break;
84 }
85}
86
88 this->resampler_.reset();
89 this->resampler_ready_ = false;
90
91 if (input_stream_info.get_sample_rate() == this->target_sample_rate_) {
92 // The source already delivers the target sample rate, so its audio is passed through unchanged
93 this->input_stream_info_ = input_stream_info;
94 this->resampler_ready_ = true;
95 return true;
96 }
97
98 const audio::AudioStreamInfo output_stream_info(input_stream_info.get_bits_per_sample(),
99 input_stream_info.get_channels(), this->target_sample_rate_);
100
101 auto resampler = make_unique<esp_audio_libs::resampler::Resampler>(
102 input_stream_info.ms_to_samples(BUFFER_DURATION_MS), output_stream_info.ms_to_samples(BUFFER_DURATION_MS));
103
104 esp_audio_libs::resampler::ResamplerConfiguration resample_config = {
105 .source_sample_rate = static_cast<float>(input_stream_info.get_sample_rate()),
106 .target_sample_rate = static_cast<float>(this->target_sample_rate_),
107 .source_bits_per_sample = input_stream_info.get_bits_per_sample(),
108 .target_bits_per_sample = input_stream_info.get_bits_per_sample(),
109 .channels = input_stream_info.get_channels(),
110 // Filters out frequencies above the new Nyquist limit when downsampling, to avoid aliasing
111 .use_pre_or_post_filter = this->target_sample_rate_ < input_stream_info.get_sample_rate(),
112 .subsample_interpolate = false, // Doubles the CPU load; more filters is a better alternative
113 .number_of_taps = this->taps_,
114 .number_of_filters = this->filters_,
115 };
116
117 if (!resampler->initialize(resample_config)) {
118 ESP_LOGE(TAG, "Not enough memory to resample");
119 return false;
120 }
121
122 this->output_buffer_.reserve(output_stream_info.ms_to_bytes(BUFFER_DURATION_MS));
123 this->resampler_ = std::move(resampler);
124 // Only set on success, so a failed set up is retried with the next chunk
125 this->input_stream_info_ = input_stream_info;
126 this->resampler_ready_ = true;
127 return true;
128}
129
130void ResamplerMicrophone::process_audio_(const std::vector<uint8_t> &data) {
131 const audio::AudioStreamInfo input_stream_info = this->source_->get_audio_stream_info();
132 if (input_stream_info != this->input_stream_info_) {
133 this->init_resampler_(input_stream_info);
134 }
135 if (!this->resampler_ready_) {
136 return;
137 }
138
139 if (this->resampler_ == nullptr) {
140 this->data_callbacks_.call(data);
141 return;
142 }
143
144 const size_t input_bytes_per_frame = input_stream_info.frames_to_bytes(1);
145 const uint32_t max_input_frames = input_stream_info.ms_to_frames(BUFFER_DURATION_MS);
146 // Both limits match the sizes the resampler's internal buffers were allocated with in init_resampler_
147 const uint32_t max_output_frames = this->audio_stream_info_.ms_to_frames(BUFFER_DURATION_MS);
148
149 const uint8_t *input = data.data();
150 uint32_t input_frames = input_stream_info.bytes_to_frames(data.size());
151 while (input_frames > 0) {
152 // Stays within the reserved capacity, so this never reallocates
153 this->output_buffer_.resize(this->audio_stream_info_.frames_to_bytes(max_output_frames));
154
155 // The resampler's internal buffers hold at most BUFFER_DURATION_MS of audio, so feed it in steps of that size.
156 // 0 dB keeps the microphone level that downstream detectors are tuned for; overshoot saturates instead of wrapping.
157 esp_audio_libs::resampler::ResamplerResults results = this->resampler_->resample(
158 input, this->output_buffer_.data(), std::min(input_frames, max_input_frames), max_output_frames, 0.0f);
159
160 input += results.frames_used * input_bytes_per_frame;
161 input_frames -= results.frames_used;
162
163 if (results.frames_generated > 0) {
164 this->output_buffer_.resize(this->audio_stream_info_.frames_to_bytes(results.frames_generated));
165 this->data_callbacks_.call(this->output_buffer_);
166 } else if (results.frames_used == 0) {
167 break; // No progress; drop the rest of the chunk instead of spinning
168 }
169 }
170}
171
172} // namespace esphome::resampler
173
174#endif // USE_ESP32
void mark_failed()
Mark this component as failed.
bool is_failed() const
Definition component.h:274
void enable_loop()
Enable this component's loop.
Definition component.h:248
void disable_loop()
Disable this component's loop.
size_t ms_to_bytes(uint32_t ms) const
Converts duration to bytes.
Definition audio.h:73
size_t frames_to_bytes(uint32_t frames) const
Converts frames to bytes.
Definition audio.h:53
uint8_t get_bits_per_sample() const
Definition audio.h:28
uint32_t ms_to_samples(uint32_t ms) const
Converts duration to samples.
Definition audio.h:68
uint32_t bytes_to_frames(size_t bytes) const
Convert bytes to frames.
Definition audio.h:43
uint8_t get_channels() const
Definition audio.h:29
uint32_t ms_to_frames(uint32_t ms) const
Converts duration to frames.
Definition audio.h:63
uint32_t get_sample_rate() const
Definition audio.h:30
audio::AudioStreamInfo audio_stream_info_
Definition microphone.h:45
CallbackManager< void(const std::vector< uint8_t > &)> data_callbacks_
Definition microphone.h:47
void add_data_callback(F &&data_callback)
audio::AudioStreamInfo get_audio_stream_info()
Gets the AudioStreamInfo of the data after processing.
bool init_resampler_(const audio::AudioStreamInfo &input_stream_info)
Sets up the resampler for the given input format.
void process_audio_(const std::vector< uint8_t > &data)
Resamples a chunk of source audio and passes it to the data callbacks. Source microphone task only.
microphone::MicrophoneSource * source_
std::unique_ptr< esp_audio_libs::resampler::Resampler > resampler_
ESPHOME_LOG_TAG(TAG, "resampler.microphone")
static void uint32_t