ESPHome 2026.5.3
Loading...
Searching...
No Matches
mixer_speaker.h
Go to the documentation of this file.
1#pragma once
2
3#ifdef USE_ESP32
4
9
13
14#include <freertos/event_groups.h>
15
16#include <atomic>
17
18namespace esphome::mixer_speaker {
19
20/* Classes for mixing several source speaker audio streams and writing it to another speaker component.
21 * - Volume controls are passed through to the output speaker
22 * - Source speaker commands are signaled via event group bits and processed in its loop function to ensure thread
23 * safety
24 * - Directly handles pausing at the SourceSpeaker level; pause state is not passed through to the output speaker.
25 * - Audio sent to the SourceSpeaker must have 16 bits per sample.
26 * - Audio sent to the SourceSpeaker can have any number of channels. They are duplicated or ignored as needed to match
27 * the number of channels required for the output speaker.
28 * - In queue mode, the audio sent to the SourceSpeakers can have different sample rates.
29 * - In non-queue mode, the audio sent to the SourceSpeakers must have the same sample rates.
30 * - SourceSpeaker has an internal ring buffer. It also allocates a shared_ptr for an AudioTranserBuffer object.
31 * - Audio Data Flow:
32 * - Audio data played on a SourceSpeaker first writes to its internal ring buffer.
33 * - MixerSpeaker task temporarily takes shared ownership of each SourceSpeaker's AudioTransferBuffer.
34 * - MixerSpeaker calls SourceSpeaker's `process_data_from_source`, which transfers audio from the SourceSpeaker's
35 * ring buffer to its AudioTransferBuffer. Audio ducking is applied at this step.
36 * - In queue mode, MixerSpeaker prioritizes the earliest configured SourceSpeaker with audio data. Audio data is
37 * sent to the output speaker.
38 * - In non-queue mode, MixerSpeaker adds all the audio data in each SourceSpeaker into one stream that is written
39 * to the output speaker.
40 */
41
42class MixerSpeaker;
43
45 public:
46 void dump_config() override;
47 void setup() override;
48 void loop() override;
49
50 size_t play(const uint8_t *data, size_t length, TickType_t ticks_to_wait) override;
51 size_t play(const uint8_t *data, size_t length) override { return this->play(data, length, 0); }
52
53 void start() override;
54 void stop() override;
55 void finish() override;
56
57 bool has_buffered_data() const override;
58
60 void set_mute_state(bool mute_state) override;
61 bool get_mute_state() override;
62
64 void set_volume(float volume) override;
65 float get_volume() override;
66
67 void set_pause_state(bool pause_state) override { this->pause_state_ = pause_state; }
68 bool get_pause_state() const override { return this->pause_state_; }
69
76 size_t process_data_from_source(std::shared_ptr<audio::RingBufferAudioSource> &audio_source,
77 TickType_t ticks_to_wait);
78
82 void apply_ducking(uint8_t decibel_reduction, uint32_t duration);
83
84 void set_buffer_duration(uint32_t buffer_duration_ms) { this->buffer_duration_ms_ = buffer_duration_ms; }
85 void set_parent(MixerSpeaker *parent) { this->parent_ = parent; }
86 void set_timeout(uint32_t ms) { this->timeout_ms_ = ms; }
87
88 std::weak_ptr<audio::RingBufferAudioSource> get_audio_source() { return this->audio_source_; }
89
90 protected:
91 friend class MixerSpeaker;
92 esp_err_t start_();
94 void send_command_(uint32_t command_bit, bool wake_loop = false);
95
105 static void duck_samples(int16_t *input_buffer, uint32_t input_samples_to_duck, int8_t *current_ducking_db_reduction,
106 uint32_t *ducking_transition_samples_remaining, uint32_t samples_per_ducking_step,
107 int8_t db_change_per_ducking_step);
108
110
111 std::shared_ptr<audio::RingBufferAudioSource> audio_source_;
112 std::weak_ptr<ring_buffer::RingBuffer> ring_buffer_;
113
116 optional<uint32_t> timeout_ms_;
117 bool stop_gracefully_{false};
118
119 bool pause_state_{false};
120
126
127 std::atomic<uint32_t> pending_playback_frames_{0};
128 std::atomic<uint32_t> playback_delay_frames_{0}; // Frames in output pipeline when this source started contributing
129 std::atomic<bool> has_contributed_{false}; // Tracks if source has contributed during this session
130
131 EventGroupHandle_t event_group_{nullptr};
133};
134
135class MixerSpeaker : public Component {
136 public:
137 void dump_config() override;
138 void setup() override;
139 void loop() override;
140
141 void init_source_speakers(size_t count) { this->source_speakers_.init(count); }
142 void add_source_speaker(SourceSpeaker *source_speaker) { this->source_speakers_.push_back(source_speaker); }
143
149 esp_err_t start(audio::AudioStreamInfo &stream_info);
150
151 void set_output_channels(uint8_t output_channels) { this->output_channels_ = output_channels; }
152 void set_output_speaker(speaker::Speaker *speaker) { this->output_speaker_ = speaker; }
153 void set_queue_mode(bool queue_mode) { this->queue_mode_ = queue_mode; }
154 void set_task_stack_in_psram(bool task_stack_in_psram) { this->task_stack_in_psram_ = task_stack_in_psram; }
155
157
159 uint32_t get_frames_in_pipeline() const { return this->frames_in_pipeline_.load(std::memory_order_acquire); }
160
161 protected:
170 static void copy_frames(const int16_t *input_buffer, audio::AudioStreamInfo input_stream_info, int16_t *output_buffer,
171 audio::AudioStreamInfo output_stream_info, uint32_t frames_to_transfer);
172
184 static void mix_audio_samples(const int16_t *primary_buffer, audio::AudioStreamInfo primary_stream_info,
185 const int16_t *secondary_buffer, audio::AudioStreamInfo secondary_stream_info,
186 int16_t *output_buffer, audio::AudioStreamInfo output_stream_info,
187 uint32_t frames_to_mix);
188
189 static void audio_mixer_task(void *params);
190
191 EventGroupHandle_t event_group_{nullptr};
192
195
199
201
202 optional<audio::AudioStreamInfo> audio_stream_info_;
203
204 std::atomic<uint32_t> frames_in_pipeline_{0}; // Frames written to output but not yet played
205 uint32_t all_stopped_since_ms_{0}; // Debounce transient all-stopped windows before stopping task
206};
207
208} // namespace esphome::mixer_speaker
209
210#endif
Fixed-capacity vector - allocates once at runtime, never reallocates This avoids std::vector template...
Definition helpers.h:529
Helper for FreeRTOS static task management.
Definition static_task.h:15
uint32_t get_frames_in_pipeline() const
Returns the current number of frames in the output pipeline (written but not yet played)
esp_err_t start(audio::AudioStreamInfo &stream_info)
Starts the mixer task.
void set_output_channels(uint8_t output_channels)
void add_source_speaker(SourceSpeaker *source_speaker)
FixedVector< SourceSpeaker * > source_speakers_
void set_task_stack_in_psram(bool task_stack_in_psram)
speaker::Speaker * get_output_speaker() const
void set_output_speaker(speaker::Speaker *speaker)
static void mix_audio_samples(const int16_t *primary_buffer, audio::AudioStreamInfo primary_stream_info, const int16_t *secondary_buffer, audio::AudioStreamInfo secondary_stream_info, int16_t *output_buffer, audio::AudioStreamInfo output_stream_info, uint32_t frames_to_mix)
Mixes the primary and secondary streams taking into account the number of channels in each stream.
static void copy_frames(const int16_t *input_buffer, audio::AudioStreamInfo input_stream_info, int16_t *output_buffer, audio::AudioStreamInfo output_stream_info, uint32_t frames_to_transfer)
Copies audio frames from the input buffer to the output buffer taking into account the number of chan...
std::atomic< uint32_t > frames_in_pipeline_
static void audio_mixer_task(void *params)
optional< audio::AudioStreamInfo > audio_stream_info_
void set_mute_state(bool mute_state) override
Mute state changes are passed to the parent's output speaker.
void set_buffer_duration(uint32_t buffer_duration_ms)
static void duck_samples(int16_t *input_buffer, uint32_t input_samples_to_duck, int8_t *current_ducking_db_reduction, uint32_t *ducking_transition_samples_remaining, uint32_t samples_per_ducking_step, int8_t db_change_per_ducking_step)
Ducks audio samples by a specified amount.
void send_command_(uint32_t command_bit, bool wake_loop=false)
bool get_pause_state() const override
std::shared_ptr< audio::RingBufferAudioSource > audio_source_
std::atomic< uint32_t > playback_delay_frames_
std::weak_ptr< ring_buffer::RingBuffer > ring_buffer_
void apply_ducking(uint8_t decibel_reduction, uint32_t duration)
Sets the ducking level for the source speaker.
std::weak_ptr< audio::RingBufferAudioSource > get_audio_source()
size_t play(const uint8_t *data, size_t length) override
size_t play(const uint8_t *data, size_t length, TickType_t ticks_to_wait) override
size_t process_data_from_source(std::shared_ptr< audio::RingBufferAudioSource > &audio_source, TickType_t ticks_to_wait)
Exposes the next ring buffer chunk (zero-copy) and ducks the freshly exposed bytes in place.
void set_parent(MixerSpeaker *parent)
void set_volume(float volume) override
Volume state changes are passed to the parent's output speaker.
void set_pause_state(bool pause_state) override
std::atomic< uint32_t > pending_playback_frames_
uint8_t duration
Definition msa3xx.h:0
static void uint32_t
uint16_t length
Definition tt21100.cpp:0