ESPHome 2025.5.2
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Modules Pages
mixer_speaker.cpp
Go to the documentation of this file.
1#include "mixer_speaker.h"
2
3#ifdef USE_ESP32
4
5#include "esphome/core/hal.h"
7#include "esphome/core/log.h"
8
9#include <algorithm>
10#include <cstring>
11
12namespace esphome {
13namespace mixer_speaker {
14
15static const UBaseType_t MIXER_TASK_PRIORITY = 10;
16
17static const uint32_t TRANSFER_BUFFER_DURATION_MS = 50;
18static const uint32_t TASK_DELAY_MS = 25;
19
20static const size_t TASK_STACK_SIZE = 4096;
21
22static const int16_t MAX_AUDIO_SAMPLE_VALUE = INT16_MAX;
23static const int16_t MIN_AUDIO_SAMPLE_VALUE = INT16_MIN;
24
25static const char *const TAG = "speaker_mixer";
26
27// Gives the Q15 fixed point scaling factor to reduce by 0 dB, 1dB, ..., 50 dB
28// dB to PCM scaling factor formula: floating_point_scale_factor = 2^(-db/6.014)
29// float to Q15 fixed point formula: q15_scale_factor = floating_point_scale_factor * 2^(15)
30static const std::vector<int16_t> DECIBEL_REDUCTION_TABLE = {
31 32767, 29201, 26022, 23189, 20665, 18415, 16410, 14624, 13032, 11613, 10349, 9222, 8218, 7324, 6527, 5816, 5183,
32 4619, 4116, 3668, 3269, 2913, 2596, 2313, 2061, 1837, 1637, 1459, 1300, 1158, 1032, 920, 820, 731,
33 651, 580, 517, 461, 411, 366, 326, 291, 259, 231, 206, 183, 163, 146, 130, 116, 103};
34
35enum MixerEventGroupBits : uint32_t {
36 COMMAND_STOP = (1 << 0), // stops the mixer task
37 STATE_STARTING = (1 << 10),
38 STATE_RUNNING = (1 << 11),
39 STATE_STOPPING = (1 << 12),
40 STATE_STOPPED = (1 << 13),
41 ERR_ESP_NO_MEM = (1 << 19),
42 ALL_BITS = 0x00FFFFFF, // All valid FreeRTOS event group bits
43};
44
46 ESP_LOGCONFIG(TAG, "Mixer Source Speaker");
47 ESP_LOGCONFIG(TAG, " Buffer Duration: %" PRIu32 " ms", this->buffer_duration_ms_);
48 if (this->timeout_ms_.has_value()) {
49 ESP_LOGCONFIG(TAG, " Timeout: %" PRIu32 " ms", this->timeout_ms_.value());
50 } else {
51 ESP_LOGCONFIG(TAG, " Timeout: never");
52 }
53}
54
56 this->parent_->get_output_speaker()->add_audio_output_callback([this](uint32_t new_frames, int64_t write_timestamp) {
57 // The SourceSpeaker may not have included any audio in the mixed output, so verify there were pending frames
58 uint32_t speakers_playback_frames = std::min(new_frames, this->pending_playback_frames_);
59 this->pending_playback_frames_ -= speakers_playback_frames;
60
61 if (speakers_playback_frames > 0) {
62 this->audio_output_callback_(speakers_playback_frames, write_timestamp);
63 }
64 });
65}
66
68 switch (this->state_) {
70 esp_err_t err = this->start_();
71 if (err == ESP_OK) {
73 this->stop_gracefully_ = false;
74 this->last_seen_data_ms_ = millis();
75 this->status_clear_error();
76 } else {
77 switch (err) {
78 case ESP_ERR_NO_MEM:
79 this->status_set_error("Failed to start mixer: not enough memory");
80 break;
81 case ESP_ERR_NOT_SUPPORTED:
82 this->status_set_error("Failed to start mixer: unsupported bits per sample");
83 break;
84 case ESP_ERR_INVALID_ARG:
85 this->status_set_error("Failed to start mixer: audio stream isn't compatible with the other audio stream.");
86 break;
87 case ESP_ERR_INVALID_STATE:
88 this->status_set_error("Failed to start mixer: mixer task failed to start");
89 break;
90 default:
91 this->status_set_error("Failed to start mixer");
92 break;
93 }
94
96 }
97 break;
98 }
100 if (!this->transfer_buffer_->has_buffered_data()) {
101 if ((this->timeout_ms_.has_value() && ((millis() - this->last_seen_data_ms_) > this->timeout_ms_.value())) ||
102 this->stop_gracefully_) {
104 }
105 }
106 break;
108 this->stop_();
109 this->stop_gracefully_ = false;
111 break;
113 break;
114 }
115}
116
117size_t SourceSpeaker::play(const uint8_t *data, size_t length, TickType_t ticks_to_wait) {
118 if (this->is_stopped()) {
119 this->start();
120 }
121 size_t bytes_written = 0;
122 if (this->ring_buffer_.use_count() == 1) {
123 std::shared_ptr<RingBuffer> temp_ring_buffer = this->ring_buffer_.lock();
124 bytes_written = temp_ring_buffer->write_without_replacement(data, length, ticks_to_wait);
125 if (bytes_written > 0) {
126 this->last_seen_data_ms_ = millis();
127 }
128 }
129 return bytes_written;
130}
131
133
135 const size_t ring_buffer_size = this->audio_stream_info_.ms_to_bytes(this->buffer_duration_ms_);
136 if (this->transfer_buffer_.use_count() == 0) {
137 this->transfer_buffer_ =
139
140 if (this->transfer_buffer_ == nullptr) {
141 return ESP_ERR_NO_MEM;
142 }
143 std::shared_ptr<RingBuffer> temp_ring_buffer;
144
145 if (!this->ring_buffer_.use_count()) {
146 temp_ring_buffer = RingBuffer::create(ring_buffer_size);
147 this->ring_buffer_ = temp_ring_buffer;
148 }
149
150 if (!this->ring_buffer_.use_count()) {
151 return ESP_ERR_NO_MEM;
152 } else {
153 this->transfer_buffer_->set_source(temp_ring_buffer);
154 }
155 }
156
157 this->pending_playback_frames_ = 0; // reset
158 return this->parent_->start(this->audio_stream_info_);
159}
160
162 if (this->state_ != speaker::STATE_STOPPED) {
164 }
165}
166
168 this->transfer_buffer_.reset(); // deallocates the transfer buffer
169}
170
172
174 return ((this->transfer_buffer_.use_count() > 0) && this->transfer_buffer_->has_buffered_data());
175}
176
177void SourceSpeaker::set_mute_state(bool mute_state) {
178 this->mute_state_ = mute_state;
179 this->parent_->get_output_speaker()->set_mute_state(mute_state);
180}
181
183
184void SourceSpeaker::set_volume(float volume) {
185 this->volume_ = volume;
186 this->parent_->get_output_speaker()->set_volume(volume);
187}
188
190
191size_t SourceSpeaker::process_data_from_source(TickType_t ticks_to_wait) {
192 if (!this->transfer_buffer_.use_count()) {
193 return 0;
194 }
195
196 // Store current offset, as these samples are already ducked
197 const size_t current_length = this->transfer_buffer_->available();
198
199 size_t bytes_read = this->transfer_buffer_->transfer_data_from_source(ticks_to_wait);
200
201 uint32_t samples_to_duck = this->audio_stream_info_.bytes_to_samples(bytes_read);
202 if (samples_to_duck > 0) {
203 int16_t *current_buffer = reinterpret_cast<int16_t *>(this->transfer_buffer_->get_buffer_start() + current_length);
204
205 duck_samples(current_buffer, samples_to_duck, &this->current_ducking_db_reduction_,
208 }
209
210 return bytes_read;
211}
212
213void SourceSpeaker::apply_ducking(uint8_t decibel_reduction, uint32_t duration) {
214 if (this->target_ducking_db_reduction_ != decibel_reduction) {
216
217 this->target_ducking_db_reduction_ = decibel_reduction;
218
219 uint8_t total_ducking_steps = 0;
221 // The dB reduction level is increasing (which results in quieter audio)
222 total_ducking_steps = this->target_ducking_db_reduction_ - this->current_ducking_db_reduction_ - 1;
224 } else {
225 // The dB reduction level is decreasing (which results in louder audio)
226 total_ducking_steps = this->current_ducking_db_reduction_ - this->target_ducking_db_reduction_ - 1;
228 }
229 if ((duration > 0) && (total_ducking_steps > 0)) {
231
232 this->samples_per_ducking_step_ = this->ducking_transition_samples_remaining_ / total_ducking_steps;
234 this->samples_per_ducking_step_ * total_ducking_steps; // Adjust for integer division rounding
235
237 } else {
240 }
241 }
242}
243
244void SourceSpeaker::duck_samples(int16_t *input_buffer, uint32_t input_samples_to_duck,
245 int8_t *current_ducking_db_reduction, uint32_t *ducking_transition_samples_remaining,
246 uint32_t samples_per_ducking_step, int8_t db_change_per_ducking_step) {
247 if (*ducking_transition_samples_remaining > 0) {
248 // Ducking level is still transitioning
249
250 // Takes the ceiling of input_samples_to_duck/samples_per_ducking_step
251 uint32_t ducking_steps_in_batch =
252 input_samples_to_duck / samples_per_ducking_step + (input_samples_to_duck % samples_per_ducking_step != 0);
253
254 for (uint32_t i = 0; i < ducking_steps_in_batch; ++i) {
255 uint32_t samples_left_in_step = *ducking_transition_samples_remaining % samples_per_ducking_step;
256
257 if (samples_left_in_step == 0) {
258 samples_left_in_step = samples_per_ducking_step;
259 }
260
261 uint32_t samples_to_duck = std::min(input_samples_to_duck, samples_left_in_step);
262 samples_to_duck = std::min(samples_to_duck, *ducking_transition_samples_remaining);
263
264 // Ensure we only point to valid index in the Q15 scaling factor table
265 uint8_t safe_db_reduction_index =
266 clamp<uint8_t>(*current_ducking_db_reduction, 0, DECIBEL_REDUCTION_TABLE.size() - 1);
267 int16_t q15_scale_factor = DECIBEL_REDUCTION_TABLE[safe_db_reduction_index];
268
269 audio::scale_audio_samples(input_buffer, input_buffer, q15_scale_factor, samples_to_duck);
270
271 if (samples_left_in_step - samples_to_duck == 0) {
272 // After scaling the current samples, we are ready to transition to the next step
273 *current_ducking_db_reduction += db_change_per_ducking_step;
274 }
275
276 input_buffer += samples_to_duck;
277 *ducking_transition_samples_remaining -= samples_to_duck;
278 input_samples_to_duck -= samples_to_duck;
279 }
280 }
281
282 if ((*current_ducking_db_reduction > 0) && (input_samples_to_duck > 0)) {
283 // Audio is ducked, but its not in the middle of a transition step
284
285 uint8_t safe_db_reduction_index =
286 clamp<uint8_t>(*current_ducking_db_reduction, 0, DECIBEL_REDUCTION_TABLE.size() - 1);
287 int16_t q15_scale_factor = DECIBEL_REDUCTION_TABLE[safe_db_reduction_index];
288
289 audio::scale_audio_samples(input_buffer, input_buffer, q15_scale_factor, input_samples_to_duck);
290 }
291}
292
294 ESP_LOGCONFIG(TAG, "Speaker Mixer:");
295 ESP_LOGCONFIG(TAG, " Number of output channels: %u", this->output_channels_);
296}
297
299 this->event_group_ = xEventGroupCreate();
300
301 if (this->event_group_ == nullptr) {
302 ESP_LOGE(TAG, "Failed to create event group");
303 this->mark_failed();
304 return;
305 }
306}
307
309 uint32_t event_group_bits = xEventGroupGetBits(this->event_group_);
310
311 if (event_group_bits & MixerEventGroupBits::STATE_STARTING) {
312 ESP_LOGD(TAG, "Starting speaker mixer");
313 xEventGroupClearBits(this->event_group_, MixerEventGroupBits::STATE_STARTING);
314 }
315 if (event_group_bits & MixerEventGroupBits::ERR_ESP_NO_MEM) {
316 this->status_set_error("Failed to allocate the mixer's internal buffer");
317 xEventGroupClearBits(this->event_group_, MixerEventGroupBits::ERR_ESP_NO_MEM);
318 }
319 if (event_group_bits & MixerEventGroupBits::STATE_RUNNING) {
320 ESP_LOGD(TAG, "Started speaker mixer");
321 this->status_clear_error();
322 xEventGroupClearBits(this->event_group_, MixerEventGroupBits::STATE_RUNNING);
323 }
324 if (event_group_bits & MixerEventGroupBits::STATE_STOPPING) {
325 ESP_LOGD(TAG, "Stopping speaker mixer");
326 xEventGroupClearBits(this->event_group_, MixerEventGroupBits::STATE_STOPPING);
327 }
328 if (event_group_bits & MixerEventGroupBits::STATE_STOPPED) {
329 if (this->delete_task_() == ESP_OK) {
330 xEventGroupClearBits(this->event_group_, MixerEventGroupBits::ALL_BITS);
331 }
332 }
333
334 if (this->task_handle_ != nullptr) {
335 bool all_stopped = true;
336
337 for (auto &speaker : this->source_speakers_) {
338 all_stopped &= speaker->is_stopped();
339 }
340
341 if (all_stopped) {
342 this->stop();
343 }
344 }
345}
346
348 if (!this->audio_stream_info_.has_value()) {
349 if (stream_info.get_bits_per_sample() != 16) {
350 // Audio streams that don't have 16 bits per sample are not supported
351 return ESP_ERR_NOT_SUPPORTED;
352 }
353
354 this->audio_stream_info_ = audio::AudioStreamInfo(stream_info.get_bits_per_sample(), this->output_channels_,
355 stream_info.get_sample_rate());
357 } else {
358 if (!this->queue_mode_ && (stream_info.get_sample_rate() != this->audio_stream_info_.value().get_sample_rate())) {
359 // The two audio streams must have the same sample rate to mix properly if not in queue mode
360 return ESP_ERR_INVALID_ARG;
361 }
362 }
363
364 return this->start_task_();
365}
366
368 if (this->task_stack_buffer_ == nullptr) {
369 if (this->task_stack_in_psram_) {
371 this->task_stack_buffer_ = stack_allocator.allocate(TASK_STACK_SIZE);
372 } else {
374 this->task_stack_buffer_ = stack_allocator.allocate(TASK_STACK_SIZE);
375 }
376 }
377
378 if (this->task_stack_buffer_ == nullptr) {
379 return ESP_ERR_NO_MEM;
380 }
381
382 if (this->task_handle_ == nullptr) {
383 this->task_handle_ = xTaskCreateStatic(audio_mixer_task, "mixer", TASK_STACK_SIZE, (void *) this,
384 MIXER_TASK_PRIORITY, this->task_stack_buffer_, &this->task_stack_);
385 }
386
387 if (this->task_handle_ == nullptr) {
388 return ESP_ERR_INVALID_STATE;
389 }
390
391 return ESP_OK;
392}
393
395 if (!this->task_created_) {
396 this->task_handle_ = nullptr;
397
398 if (this->task_stack_buffer_ != nullptr) {
399 if (this->task_stack_in_psram_) {
401 stack_allocator.deallocate(this->task_stack_buffer_, TASK_STACK_SIZE);
402 } else {
404 stack_allocator.deallocate(this->task_stack_buffer_, TASK_STACK_SIZE);
405 }
406
407 this->task_stack_buffer_ = nullptr;
408 }
409
410 return ESP_OK;
411 }
412
413 return ESP_ERR_INVALID_STATE;
414}
415
417
418void MixerSpeaker::copy_frames(const int16_t *input_buffer, audio::AudioStreamInfo input_stream_info,
419 int16_t *output_buffer, audio::AudioStreamInfo output_stream_info,
420 uint32_t frames_to_transfer) {
421 uint8_t input_channels = input_stream_info.get_channels();
422 uint8_t output_channels = output_stream_info.get_channels();
423 const uint8_t max_input_channel_index = input_channels - 1;
424
425 if (input_channels == output_channels) {
426 size_t bytes_to_copy = input_stream_info.frames_to_bytes(frames_to_transfer);
427 memcpy(output_buffer, input_buffer, bytes_to_copy);
428
429 return;
430 }
431
432 for (uint32_t frame_index = 0; frame_index < frames_to_transfer; ++frame_index) {
433 for (uint8_t output_channel_index = 0; output_channel_index < output_channels; ++output_channel_index) {
434 uint8_t input_channel_index = std::min(output_channel_index, max_input_channel_index);
435 output_buffer[output_channels * frame_index + output_channel_index] =
436 input_buffer[input_channels * frame_index + input_channel_index];
437 }
438 }
439}
440
441void MixerSpeaker::mix_audio_samples(const int16_t *primary_buffer, audio::AudioStreamInfo primary_stream_info,
442 const int16_t *secondary_buffer, audio::AudioStreamInfo secondary_stream_info,
443 int16_t *output_buffer, audio::AudioStreamInfo output_stream_info,
444 uint32_t frames_to_mix) {
445 const uint8_t primary_channels = primary_stream_info.get_channels();
446 const uint8_t secondary_channels = secondary_stream_info.get_channels();
447 const uint8_t output_channels = output_stream_info.get_channels();
448
449 const uint8_t max_primary_channel_index = primary_channels - 1;
450 const uint8_t max_secondary_channel_index = secondary_channels - 1;
451
452 for (uint32_t frames_index = 0; frames_index < frames_to_mix; ++frames_index) {
453 for (uint8_t output_channel_index = 0; output_channel_index < output_channels; ++output_channel_index) {
454 const uint32_t secondary_channel_index = std::min(output_channel_index, max_secondary_channel_index);
455 const int32_t secondary_sample = secondary_buffer[frames_index * secondary_channels + secondary_channel_index];
456
457 const uint32_t primary_channel_index = std::min(output_channel_index, max_primary_channel_index);
458 const int32_t primary_sample =
459 static_cast<int32_t>(primary_buffer[frames_index * primary_channels + primary_channel_index]);
460
461 const int32_t added_sample = secondary_sample + primary_sample;
462
463 output_buffer[frames_index * output_channels + output_channel_index] =
464 static_cast<int16_t>(clamp<int32_t>(added_sample, MIN_AUDIO_SAMPLE_VALUE, MAX_AUDIO_SAMPLE_VALUE));
465 }
466 }
467}
468
470 MixerSpeaker *this_mixer = (MixerSpeaker *) params;
471
472 xEventGroupSetBits(this_mixer->event_group_, MixerEventGroupBits::STATE_STARTING);
473
474 this_mixer->task_created_ = true;
475
476 std::unique_ptr<audio::AudioSinkTransferBuffer> output_transfer_buffer = audio::AudioSinkTransferBuffer::create(
477 this_mixer->audio_stream_info_.value().ms_to_bytes(TRANSFER_BUFFER_DURATION_MS));
478
479 if (output_transfer_buffer == nullptr) {
480 xEventGroupSetBits(this_mixer->event_group_,
482
483 this_mixer->task_created_ = false;
484 vTaskDelete(nullptr);
485 }
486
487 output_transfer_buffer->set_sink(this_mixer->output_speaker_);
488
489 xEventGroupSetBits(this_mixer->event_group_, MixerEventGroupBits::STATE_RUNNING);
490
491 bool sent_finished = false;
492
493 while (true) {
494 uint32_t event_group_bits = xEventGroupGetBits(this_mixer->event_group_);
495 if (event_group_bits & MixerEventGroupBits::COMMAND_STOP) {
496 break;
497 }
498
499 // Never shift the data in the output transfer buffer to avoid unnecessary, slow data moves
500 output_transfer_buffer->transfer_data_to_sink(pdMS_TO_TICKS(TASK_DELAY_MS), false);
501
502 const uint32_t output_frames_free =
503 this_mixer->audio_stream_info_.value().bytes_to_frames(output_transfer_buffer->free());
504
505 std::vector<SourceSpeaker *> speakers_with_data;
506 std::vector<std::shared_ptr<audio::AudioSourceTransferBuffer>> transfer_buffers_with_data;
507
508 for (auto &speaker : this_mixer->source_speakers_) {
509 if (speaker->get_transfer_buffer().use_count() > 0) {
510 std::shared_ptr<audio::AudioSourceTransferBuffer> transfer_buffer = speaker->get_transfer_buffer().lock();
511 speaker->process_data_from_source(0); // Transfers and ducks audio from source ring buffers
512
513 if ((transfer_buffer->available() > 0) && !speaker->get_pause_state()) {
514 // Store the locked transfer buffers in their own vector to avoid releasing ownership until after the loop
515 transfer_buffers_with_data.push_back(transfer_buffer);
516 speakers_with_data.push_back(speaker);
517 }
518 }
519 }
520
521 if (transfer_buffers_with_data.empty()) {
522 // No audio available for transferring, block task temporarily
523 delay(TASK_DELAY_MS);
524 continue;
525 }
526
527 uint32_t frames_to_mix = output_frames_free;
528
529 if ((transfer_buffers_with_data.size() == 1) || this_mixer->queue_mode_) {
530 // Only one speaker has audio data, just copy samples over
531
532 audio::AudioStreamInfo active_stream_info = speakers_with_data[0]->get_audio_stream_info();
533
534 if (active_stream_info.get_sample_rate() ==
536 // Speaker's sample rate matches the output speaker's, copy directly
537
538 const uint32_t frames_available_in_buffer =
539 active_stream_info.bytes_to_frames(transfer_buffers_with_data[0]->available());
540 frames_to_mix = std::min(frames_to_mix, frames_available_in_buffer);
541 copy_frames(reinterpret_cast<int16_t *>(transfer_buffers_with_data[0]->get_buffer_start()), active_stream_info,
542 reinterpret_cast<int16_t *>(output_transfer_buffer->get_buffer_end()),
543 this_mixer->audio_stream_info_.value(), frames_to_mix);
544
545 // Update source speaker buffer length
546 transfer_buffers_with_data[0]->decrease_buffer_length(active_stream_info.frames_to_bytes(frames_to_mix));
547 speakers_with_data[0]->pending_playback_frames_ += frames_to_mix;
548
549 // Update output transfer buffer length
550 output_transfer_buffer->increase_buffer_length(
551 this_mixer->audio_stream_info_.value().frames_to_bytes(frames_to_mix));
552 } else {
553 // Speaker's stream info doesn't match the output speaker's, so it's a new source speaker
554 if (!this_mixer->output_speaker_->is_stopped()) {
555 if (!sent_finished) {
556 this_mixer->output_speaker_->finish();
557 sent_finished = true; // Avoid repeatedly sending the finish command
558 }
559 } else {
560 // Speaker has finished writing the current audio, update the stream information and restart the speaker
561 this_mixer->audio_stream_info_ =
562 audio::AudioStreamInfo(active_stream_info.get_bits_per_sample(), this_mixer->output_channels_,
563 active_stream_info.get_sample_rate());
564 this_mixer->output_speaker_->set_audio_stream_info(this_mixer->audio_stream_info_.value());
565 this_mixer->output_speaker_->start();
566 sent_finished = false;
567 }
568 }
569 } else {
570 // Determine how many frames to mix
571 for (int i = 0; i < transfer_buffers_with_data.size(); ++i) {
572 const uint32_t frames_available_in_buffer =
573 speakers_with_data[i]->get_audio_stream_info().bytes_to_frames(transfer_buffers_with_data[i]->available());
574 frames_to_mix = std::min(frames_to_mix, frames_available_in_buffer);
575 }
576 int16_t *primary_buffer = reinterpret_cast<int16_t *>(transfer_buffers_with_data[0]->get_buffer_start());
577 audio::AudioStreamInfo primary_stream_info = speakers_with_data[0]->get_audio_stream_info();
578
579 // Mix two streams together
580 for (int i = 1; i < transfer_buffers_with_data.size(); ++i) {
581 mix_audio_samples(primary_buffer, primary_stream_info,
582 reinterpret_cast<int16_t *>(transfer_buffers_with_data[i]->get_buffer_start()),
583 speakers_with_data[i]->get_audio_stream_info(),
584 reinterpret_cast<int16_t *>(output_transfer_buffer->get_buffer_end()),
585 this_mixer->audio_stream_info_.value(), frames_to_mix);
586
587 if (i != transfer_buffers_with_data.size() - 1) {
588 // Need to mix more streams together, point primary buffer and stream info to the already mixed output
589 primary_buffer = reinterpret_cast<int16_t *>(output_transfer_buffer->get_buffer_end());
590 primary_stream_info = this_mixer->audio_stream_info_.value();
591 }
592 }
593
594 // Update source transfer buffer lengths and add new audio durations to the source speaker pending playbacks
595 for (int i = 0; i < transfer_buffers_with_data.size(); ++i) {
596 transfer_buffers_with_data[i]->decrease_buffer_length(
597 speakers_with_data[i]->get_audio_stream_info().frames_to_bytes(frames_to_mix));
598 speakers_with_data[i]->pending_playback_frames_ += frames_to_mix;
599 }
600
601 // Update output transfer buffer length
602 output_transfer_buffer->increase_buffer_length(
603 this_mixer->audio_stream_info_.value().frames_to_bytes(frames_to_mix));
604 }
605 }
606
607 xEventGroupSetBits(this_mixer->event_group_, MixerEventGroupBits::STATE_STOPPING);
608
609 output_transfer_buffer.reset();
610
611 xEventGroupSetBits(this_mixer->event_group_, MixerEventGroupBits::STATE_STOPPED);
612 this_mixer->task_created_ = false;
613 vTaskDelete(nullptr);
614}
615
616} // namespace mixer_speaker
617} // namespace esphome
618
619#endif
virtual void mark_failed()
Mark this component as failed.
void status_set_error(const char *message="unspecified")
An STL allocator that uses SPI or internal RAM.
Definition helpers.h:684
void deallocate(T *p, size_t n)
Definition helpers.h:742
T * allocate(size_t n)
Definition helpers.h:704
static std::unique_ptr< RingBuffer > create(size_t len)
static std::unique_ptr< AudioSinkTransferBuffer > create(size_t buffer_size)
Creates a new sink transfer buffer.
static std::unique_ptr< AudioSourceTransferBuffer > create(size_t buffer_size)
Creates a new source transfer buffer.
size_t ms_to_bytes(uint32_t ms) const
Converts duration to bytes.
Definition audio.h:73
size_t frames_to_bytes(uint32_t frames) const
Converts frames to bytes.
Definition audio.h:53
uint8_t get_bits_per_sample() const
Definition audio.h:28
uint32_t ms_to_samples(uint32_t ms) const
Converts duration to samples.
Definition audio.h:68
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
uint32_t get_sample_rate() const
Definition audio.h:30
uint32_t bytes_to_samples(size_t bytes) const
Convert bytes to samples.
Definition audio.h:48
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.
esp_err_t start(audio::AudioStreamInfo &stream_info)
Starts the mixer task.
speaker::Speaker * get_output_speaker() const
std::vector< SourceSpeaker * > source_speakers_
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...
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.
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.
size_t process_data_from_source(TickType_t ticks_to_wait)
Transfers audio from the ring buffer into the transfer buffer.
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, TickType_t ticks_to_wait) override
void set_volume(float volume) override
Volume state changes are passed to the parent's output speaker.
bool has_value() const
Definition optional.h:87
value_type const & value() const
Definition optional.h:89
virtual void set_volume(float volume)
Definition speaker.h:71
void add_audio_output_callback(std::function< void(uint32_t, int64_t)> &&callback)
Callback function for sending the duration of the audio written to the speaker since the last callbac...
Definition speaker.h:109
virtual float get_volume()
Definition speaker.h:79
CallbackManager< void(uint32_t, int64_t)> audio_output_callback_
Definition speaker.h:123
void set_audio_stream_info(const audio::AudioStreamInfo &audio_stream_info)
Definition speaker.h:99
audio::AudioStreamInfo & get_audio_stream_info()
Definition speaker.h:103
virtual bool get_mute_state()
Definition speaker.h:93
virtual void set_mute_state(bool mute_state)
Definition speaker.h:81
audio::AudioStreamInfo audio_stream_info_
Definition speaker.h:115
virtual void start()=0
virtual void finish()
Definition speaker.h:58
bool is_stopped() const
Definition speaker.h:67
uint8_t duration
Definition msa3xx.h:0
void scale_audio_samples(const int16_t *audio_samples, int16_t *output_buffer, int16_t scale_factor, size_t samples_to_scale)
Scales Q15 fixed point audio samples.
Definition audio.cpp:57
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:29
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:28
constexpr const T & clamp(const T &v, const T &lo, const T &hi, Compare comp)
Definition helpers.h:102
uint16_t length
Definition tt21100.cpp:0