ESPHome 2025.5.0
Loading...
Searching...
No Matches
microphone_source.cpp
Go to the documentation of this file.
1#include "microphone_source.h"
2
3namespace esphome {
4namespace microphone {
5
6static const int32_t Q25_MAX_VALUE = (1 << 25) - 1;
7static const int32_t Q25_MIN_VALUE = ~Q25_MAX_VALUE;
8
9void MicrophoneSource::add_data_callback(std::function<void(const std::vector<uint8_t> &)> &&data_callback) {
10 std::function<void(const std::vector<uint8_t> &)> filtered_callback =
11 [this, data_callback](const std::vector<uint8_t> &data) {
12 if (this->enabled_ || this->passive_) {
13 if (this->processed_samples_.use_count() == 0) {
14 // Create vector if its unused
15 this->processed_samples_ = std::make_shared<std::vector<uint8_t>>();
16 }
17
18 // Take temporary ownership of samples vector to avoid deallaction before the callback finishes
19 std::shared_ptr<std::vector<uint8_t>> output_samples = this->processed_samples_;
20 this->process_audio_(data, *output_samples);
21 data_callback(*output_samples);
22 }
23 };
24 this->mic_->add_data_callback(std::move(filtered_callback));
25}
26
28 return audio::AudioStreamInfo(this->bits_per_sample_, this->channels_.count(),
29 this->mic_->get_audio_stream_info().get_sample_rate());
30}
31
33 if (!this->enabled_ && !this->passive_) {
34 this->enabled_ = true;
35 this->mic_->start();
36 }
37}
38
40 if (this->enabled_ && !this->passive_) {
41 this->enabled_ = false;
42 this->mic_->stop();
43 this->processed_samples_.reset();
44 }
45}
46
47void MicrophoneSource::process_audio_(const std::vector<uint8_t> &data, std::vector<uint8_t> &filtered_data) {
48 // - Bit depth conversions are obtained by truncating bits or padding with zeros - no dithering is applied.
49 // - In the comments, Qxx refers to a fixed point number with xx bits of precision for representing fractional values.
50 // For example, audio with a bit depth of 16 can store a sample in a int16, which can be considered a Q15 number.
51 // - All samples are converted to Q25 before applying the gain factor - this results in a small precision loss for
52 // data with 32 bits per sample. Since the maximum gain factor is 64 = (1<<6), this ensures that applying the gain
53 // will never overflow a 32 bit signed integer. This still retains more bit depth than what is audibly noticeable.
54 // - Loops for reading/writing data buffers are unrolled, assuming little endian, for a small performance increase.
55
56 const size_t source_bytes_per_sample = this->mic_->get_audio_stream_info().samples_to_bytes(1);
57 const uint32_t source_channels = this->mic_->get_audio_stream_info().get_channels();
58
59 const size_t source_bytes_per_frame = this->mic_->get_audio_stream_info().frames_to_bytes(1);
60
61 const uint32_t total_frames = this->mic_->get_audio_stream_info().bytes_to_frames(data.size());
62 const size_t target_bytes_per_sample = (this->bits_per_sample_ + 7) / 8;
63 const size_t target_bytes_per_frame = target_bytes_per_sample * this->channels_.count();
64
65 filtered_data.resize(target_bytes_per_frame * total_frames);
66
67 uint8_t *current_data = filtered_data.data();
68
69 for (uint32_t frame_index = 0; frame_index < total_frames; ++frame_index) {
70 for (uint32_t channel_index = 0; channel_index < source_channels; ++channel_index) {
71 if (this->channels_.test(channel_index)) {
72 // Channel's current sample is included in the target mask. Convert bits per sample, if necessary.
73
74 const uint32_t sample_index = frame_index * source_bytes_per_frame + channel_index * source_bytes_per_sample;
75
76 int32_t sample = audio::unpack_audio_sample_to_q31(&data[sample_index], source_bytes_per_sample); // Q31
77 sample >>= 6; // Q31 -> Q25
78
79 // Apply gain using multiplication
80 sample *= this->gain_factor_; // Q25
81
82 // Clamp ``sample`` in case gain multiplication overflows 25 bits
83 sample = clamp<int32_t>(sample, Q25_MIN_VALUE, Q25_MAX_VALUE); // Q25
84
85 sample *= (1 << 6); // Q25 -> Q31
86
87 audio::pack_q31_as_audio_sample(sample, current_data, target_bytes_per_sample);
88 current_data = current_data + target_bytes_per_sample;
89 }
90 }
91 }
92}
93
94} // namespace microphone
95} // namespace esphome
size_t frames_to_bytes(uint32_t frames) const
Converts frames to bytes.
Definition audio.h:53
size_t samples_to_bytes(uint32_t samples) const
Converts samples to bytes.
Definition audio.h:58
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
audio::AudioStreamInfo get_audio_stream_info()
Definition microphone.h:33
void add_data_callback(std::function< void(const std::vector< uint8_t > &)> &&data_callback)
Definition microphone.cpp:6
std::shared_ptr< std::vector< uint8_t > > processed_samples_
void add_data_callback(std::function< void(const std::vector< uint8_t > &)> &&data_callback)
audio::AudioStreamInfo get_audio_stream_info()
Gets the AudioStreamInfo of the data after processing.
void process_audio_(const std::vector< uint8_t > &data, std::vector< uint8_t > &filtered_data)
int32_t unpack_audio_sample_to_q31(const uint8_t *data, size_t bytes_per_sample)
Unpacks a quantized audio sample into a Q31 fixed-point number.
Definition audio.h:142
void pack_q31_as_audio_sample(int32_t sample, uint8_t *data, size_t bytes_per_sample)
Packs a Q31 fixed-point number as an audio sample with the specified number of bytes per sample.
Definition audio.h:168
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
constexpr const T & clamp(const T &v, const T &lo, const T &hi, Compare comp)
Definition helpers.h:101