ESPHome 2026.6.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
9
13
14#include <ducking.h> // esp-audio-libs
15
16#include <freertos/event_groups.h>
17
18#include <atomic>
19
20namespace esphome::mixer_speaker {
21
22/* Classes for mixing several source speaker audio streams and writing it to another speaker component.
23 * - Volume controls are passed through to the output speaker
24 * - Source speaker commands are signaled via event group bits and processed in its loop function to ensure thread
25 * safety
26 * - Directly handles pausing at the SourceSpeaker level; pause state is not passed through to the output speaker.
27 * - Audio sent to the SourceSpeaker can have 8, 16, 24, or 32 bits per sample. Each source is converted to the output
28 * speaker's bit depth as it is mixed (or copied) into the output buffer.
29 * - Audio sent to the SourceSpeaker can have any number of channels. They are duplicated or ignored as needed to match
30 * the number of channels required for the output speaker.
31 * - In queue mode, the audio sent to the SourceSpeakers can have different sample rates.
32 * - In non-queue mode, the audio sent to the SourceSpeakers must have the same sample rates.
33 * - SourceSpeaker has an internal ring buffer. It also allocates a shared_ptr for an AudioTranserBuffer object.
34 * - Audio Data Flow:
35 * - Audio data played on a SourceSpeaker first writes to its internal ring buffer.
36 * - MixerSpeaker task temporarily takes shared ownership of each SourceSpeaker's AudioTransferBuffer.
37 * - MixerSpeaker calls SourceSpeaker's `process_data_from_source`, which transfers audio from the SourceSpeaker's
38 * ring buffer to its AudioTransferBuffer. Audio ducking is applied at this step.
39 * - In queue mode, MixerSpeaker prioritizes the earliest configured SourceSpeaker with audio data. Audio data is
40 * sent to the output speaker.
41 * - In non-queue mode, MixerSpeaker adds all the audio data in each SourceSpeaker into one stream that is written
42 * to the output speaker.
43 */
44
45class MixerSpeaker;
46
48 public:
49 void dump_config() override;
50 void setup() override;
51 void loop() override;
52
53 size_t play(const uint8_t *data, size_t length, TickType_t ticks_to_wait) override;
54 size_t play(const uint8_t *data, size_t length) override { return this->play(data, length, 0); }
55
56 void start() override;
57 void stop() override;
58 void finish() override;
59
60 bool has_buffered_data() const override;
61
63 void set_mute_state(bool mute_state) override;
64 bool get_mute_state() override;
65
67 void set_volume(float volume) override;
68 float get_volume() override;
69
70 void set_pause_state(bool pause_state) override { this->pause_state_ = pause_state; }
71 bool get_pause_state() const override { return this->pause_state_; }
72
79 size_t process_data_from_source(std::shared_ptr<audio::RingBufferAudioSource> &audio_source,
80 TickType_t ticks_to_wait);
81
85 void apply_ducking(uint8_t decibel_reduction, uint32_t duration);
86
87 void set_buffer_duration(uint32_t buffer_duration_ms) { this->buffer_duration_ms_ = buffer_duration_ms; }
88 void set_parent(MixerSpeaker *parent) { this->parent_ = parent; }
89 void set_timeout(uint32_t ms) { this->timeout_ms_ = ms; }
90
91 std::weak_ptr<audio::RingBufferAudioSource> get_audio_source() { return this->audio_source_; }
92
93 protected:
94 friend class MixerSpeaker;
95 esp_err_t start_();
97 void send_command_(uint32_t command_bit, bool wake_loop = false);
98
100
101 std::shared_ptr<audio::RingBufferAudioSource> audio_source_;
102 std::weak_ptr<ring_buffer::RingBuffer> ring_buffer_;
103
106 optional<uint32_t> timeout_ms_;
107 bool stop_gracefully_{false};
108
109 bool pause_state_{false};
110
111 esp_audio_libs::ducking::DuckingState ducking_state_{};
112
113 std::atomic<uint32_t> pending_playback_frames_{0};
114 std::atomic<uint32_t> playback_delay_frames_{0}; // Frames in output pipeline when this source started contributing
115 std::atomic<bool> has_contributed_{false}; // Tracks if source has contributed during this session
116
117 EventGroupHandle_t event_group_{nullptr};
119};
120
121class MixerSpeaker : public Component {
122 public:
123 void dump_config() override;
124 void setup() override;
125 void loop() override;
126
127 void init_source_speakers(size_t count) { this->source_speakers_.init(count); }
128 void add_source_speaker(SourceSpeaker *source_speaker) { this->source_speakers_.push_back(source_speaker); }
129
134 esp_err_t start(audio::AudioStreamInfo &stream_info);
135
136 void set_output_channels(uint8_t output_channels) { this->output_channels_ = output_channels; }
137 void set_output_bits_per_sample(uint8_t output_bits_per_sample) {
138 this->output_bits_per_sample_ = output_bits_per_sample;
139 }
140 void set_output_speaker(speaker::Speaker *speaker) { this->output_speaker_ = speaker; }
141 void set_queue_mode(bool queue_mode) { this->queue_mode_ = queue_mode; }
142 void set_task_stack_in_psram(bool task_stack_in_psram) { this->task_stack_in_psram_ = task_stack_in_psram; }
143
145
147 uint32_t get_frames_in_pipeline() const { return this->frames_in_pipeline_.load(std::memory_order_acquire); }
148
149 protected:
150 static void audio_mixer_task(void *params);
151
152 EventGroupHandle_t event_group_{nullptr};
153
156
161
163
164 optional<audio::AudioStreamInfo> audio_stream_info_;
165
166 std::atomic<uint32_t> frames_in_pipeline_{0}; // Frames written to output but not yet played
167 uint32_t all_stopped_since_ms_{0}; // Debounce transient all-stopped windows before stopping task
168};
169
170} // namespace esphome::mixer_speaker
171
172#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)
void set_output_bits_per_sample(uint8_t output_bits_per_sample)
std::atomic< uint32_t > frames_in_pipeline_
static void audio_mixer_task(void *params)
optional< audio::AudioStreamInfo > audio_stream_info_
esp_audio_libs::ducking::DuckingState ducking_state_
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)
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