ESPHome 2026.5.0-dev
Loading...
Searching...
No Matches
mixer_speaker.h
Go to the documentation of this file.
1#pragma once
2
3#ifdef USE_ESP32
4
8
12
13#include <freertos/event_groups.h>
14
15#include <atomic>
16
17namespace esphome::mixer_speaker {
18
19/* Classes for mixing several source speaker audio streams and writing it to another speaker component.
20 * - Volume controls are passed through to the output speaker
21 * - Source speaker commands are signaled via event group bits and processed in its loop function to ensure thread
22 * safety
23 * - Directly handles pausing at the SourceSpeaker level; pause state is not passed through to the output speaker.
24 * - Audio sent to the SourceSpeaker must have 16 bits per sample.
25 * - Audio sent to the SourceSpeaker can have any number of channels. They are duplicated or ignored as needed to match
26 * the number of channels required for the output speaker.
27 * - In queue mode, the audio sent to the SourceSpeakers can have different sample rates.
28 * - In non-queue mode, the audio sent to the SourceSpeakers must have the same sample rates.
29 * - SourceSpeaker has an internal ring buffer. It also allocates a shared_ptr for an AudioTranserBuffer object.
30 * - Audio Data Flow:
31 * - Audio data played on a SourceSpeaker first writes to its internal ring buffer.
32 * - MixerSpeaker task temporarily takes shared ownership of each SourceSpeaker's AudioTransferBuffer.
33 * - MixerSpeaker calls SourceSpeaker's `process_data_from_source`, which transfers audio from the SourceSpeaker's
34 * ring buffer to its AudioTransferBuffer. Audio ducking is applied at this step.
35 * - In queue mode, MixerSpeaker prioritizes the earliest configured SourceSpeaker with audio data. Audio data is
36 * sent to the output speaker.
37 * - In non-queue mode, MixerSpeaker adds all the audio data in each SourceSpeaker into one stream that is written
38 * to the output speaker.
39 */
40
41class MixerSpeaker;
42
44 public:
45 void dump_config() override;
46 void setup() override;
47 void loop() override;
48
49 size_t play(const uint8_t *data, size_t length, TickType_t ticks_to_wait) override;
50 size_t play(const uint8_t *data, size_t length) override { return this->play(data, length, 0); }
51
52 void start() override;
53 void stop() override;
54 void finish() override;
55
56 bool has_buffered_data() const override;
57
59 void set_mute_state(bool mute_state) override;
60 bool get_mute_state() override;
61
63 void set_volume(float volume) override;
64 float get_volume() override;
65
66 void set_pause_state(bool pause_state) override { this->pause_state_ = pause_state; }
67 bool get_pause_state() const override { return this->pause_state_; }
68
73 size_t process_data_from_source(std::shared_ptr<audio::AudioSourceTransferBuffer> &transfer_buffer,
74 TickType_t ticks_to_wait);
75
79 void apply_ducking(uint8_t decibel_reduction, uint32_t duration);
80
81 void set_buffer_duration(uint32_t buffer_duration_ms) { this->buffer_duration_ms_ = buffer_duration_ms; }
82 void set_parent(MixerSpeaker *parent) { this->parent_ = parent; }
83 void set_timeout(uint32_t ms) { this->timeout_ms_ = ms; }
84
85 std::weak_ptr<audio::AudioSourceTransferBuffer> get_transfer_buffer() { return this->transfer_buffer_; }
86
87 protected:
88 friend class MixerSpeaker;
89 esp_err_t start_();
91 void send_command_(uint32_t command_bit, bool wake_loop = false);
92
102 static void duck_samples(int16_t *input_buffer, uint32_t input_samples_to_duck, int8_t *current_ducking_db_reduction,
103 uint32_t *ducking_transition_samples_remaining, uint32_t samples_per_ducking_step,
104 int8_t db_change_per_ducking_step);
105
107
108 std::shared_ptr<audio::AudioSourceTransferBuffer> transfer_buffer_;
109 std::weak_ptr<RingBuffer> ring_buffer_;
110
113 optional<uint32_t> timeout_ms_;
114 bool stop_gracefully_{false};
115
116 bool pause_state_{false};
117
123
124 std::atomic<uint32_t> pending_playback_frames_{0};
125 std::atomic<uint32_t> playback_delay_frames_{0}; // Frames in output pipeline when this source started contributing
126 std::atomic<bool> has_contributed_{false}; // Tracks if source has contributed during this session
127
128 EventGroupHandle_t event_group_{nullptr};
130};
131
132class MixerSpeaker : public Component {
133 public:
134 void dump_config() override;
135 void setup() override;
136 void loop() override;
137
138 void init_source_speakers(size_t count) { this->source_speakers_.init(count); }
139 void add_source_speaker(SourceSpeaker *source_speaker) { this->source_speakers_.push_back(source_speaker); }
140
147
148 void set_output_channels(uint8_t output_channels) { this->output_channels_ = output_channels; }
149 void set_output_speaker(speaker::Speaker *speaker) { this->output_speaker_ = speaker; }
150 void set_queue_mode(bool queue_mode) { this->queue_mode_ = queue_mode; }
151 void set_task_stack_in_psram(bool task_stack_in_psram) { this->task_stack_in_psram_ = task_stack_in_psram; }
152
154
156 uint32_t get_frames_in_pipeline() const { return this->frames_in_pipeline_.load(std::memory_order_acquire); }
157
158 protected:
167 static void copy_frames(const int16_t *input_buffer, audio::AudioStreamInfo input_stream_info, int16_t *output_buffer,
168 audio::AudioStreamInfo output_stream_info, uint32_t frames_to_transfer);
169
181 static void mix_audio_samples(const int16_t *primary_buffer, audio::AudioStreamInfo primary_stream_info,
182 const int16_t *secondary_buffer, audio::AudioStreamInfo secondary_stream_info,
183 int16_t *output_buffer, audio::AudioStreamInfo output_stream_info,
184 uint32_t frames_to_mix);
185
186 static void audio_mixer_task(void *params);
187
188 EventGroupHandle_t event_group_{nullptr};
189
192
196
198
199 optional<audio::AudioStreamInfo> audio_stream_info_;
200
201 std::atomic<uint32_t> frames_in_pipeline_{0}; // Frames written to output but not yet played
202 uint32_t all_stopped_since_ms_{0}; // Debounce transient all-stopped windows before stopping task
203};
204
205} // namespace esphome::mixer_speaker
206
207#endif
audio::AudioStreamInfo stream_info
Fixed-capacity vector - allocates once at runtime, never reallocates This avoids std::vector template...
Definition helpers.h:522
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_
std::shared_ptr< audio::AudioSourceTransferBuffer > transfer_buffer_
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::weak_ptr< audio::AudioSourceTransferBuffer > get_transfer_buffer()
std::atomic< uint32_t > playback_delay_frames_
void apply_ducking(uint8_t decibel_reduction, uint32_t duration)
Sets the ducking level for the source speaker.
std::weak_ptr< RingBuffer > ring_buffer_
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
void set_parent(MixerSpeaker *parent)
size_t process_data_from_source(std::shared_ptr< audio::AudioSourceTransferBuffer > &transfer_buffer, TickType_t ticks_to_wait)
Transfers audio from the ring buffer into the transfer buffer.
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