ESPHome 2026.2.1
Loading...
Searching...
No Matches
mixer_speaker.h
Go to the documentation of this file.
1#pragma once
2
3#ifdef USE_ESP32
4
8
11
12#include <freertos/FreeRTOS.h>
13#include <freertos/event_groups.h>
14
15#include <atomic>
16
17namespace esphome {
18namespace 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
74 size_t process_data_from_source(std::shared_ptr<audio::AudioSourceTransferBuffer> &transfer_buffer,
75 TickType_t ticks_to_wait);
76
80 void apply_ducking(uint8_t decibel_reduction, uint32_t duration);
81
82 void set_buffer_duration(uint32_t buffer_duration_ms) { this->buffer_duration_ms_ = buffer_duration_ms; }
83 void set_parent(MixerSpeaker *parent) { this->parent_ = parent; }
84 void set_timeout(uint32_t ms) { this->timeout_ms_ = ms; }
85
86 std::weak_ptr<audio::AudioSourceTransferBuffer> get_transfer_buffer() { return this->transfer_buffer_; }
87
88 protected:
89 friend class MixerSpeaker;
90 esp_err_t start_();
92 void send_command_(uint32_t command_bit, bool wake_loop = false);
93
103 static void duck_samples(int16_t *input_buffer, uint32_t input_samples_to_duck, int8_t *current_ducking_db_reduction,
104 uint32_t *ducking_transition_samples_remaining, uint32_t samples_per_ducking_step,
105 int8_t db_change_per_ducking_step);
106
108
109 std::shared_ptr<audio::AudioSourceTransferBuffer> transfer_buffer_;
110 std::weak_ptr<RingBuffer> ring_buffer_;
111
115 bool stop_gracefully_{false};
116
117 bool pause_state_{false};
118
124
125 std::atomic<uint32_t> pending_playback_frames_{0};
126 std::atomic<uint32_t> playback_delay_frames_{0}; // Frames in output pipeline when this source started contributing
127 std::atomic<bool> has_contributed_{false}; // Tracks if source has contributed during this session
128
129 EventGroupHandle_t event_group_{nullptr};
131};
132
133class MixerSpeaker : public Component {
134 public:
135 void dump_config() override;
136 void setup() override;
137 void loop() override;
138
139 void init_source_speakers(size_t count) { this->source_speakers_.init(count); }
140 void add_source_speaker(SourceSpeaker *source_speaker) { this->source_speakers_.push_back(source_speaker); }
141
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
195 esp_err_t start_task_();
196
199 esp_err_t delete_task_();
200
201 EventGroupHandle_t event_group_{nullptr};
202
205
209
210 TaskHandle_t task_handle_{nullptr};
211 StaticTask_t task_stack_;
212 StackType_t *task_stack_buffer_{nullptr};
213
215
216 std::atomic<uint32_t> frames_in_pipeline_{0}; // Frames written to output but not yet played
217};
218
219} // namespace mixer_speaker
220} // namespace esphome
221
222#endif
Fixed-capacity vector - allocates once at runtime, never reallocates This avoids std::vector template...
Definition helpers.h:227
esp_err_t start_task_()
Starts the mixer task after allocating memory for the task stack.
esp_err_t delete_task_()
If the task is stopped, it sets the task handle to the nullptr and deallocates its stack.
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
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
uint16_t length
Definition tt21100.cpp:0