15namespace mixer_speaker {
17static const UBaseType_t MIXER_TASK_PRIORITY = 10;
19static const uint32_t STOPPING_TIMEOUT_MS = 5000;
20static const uint32_t TRANSFER_BUFFER_DURATION_MS = 50;
21static const uint32_t TASK_DELAY_MS = 25;
23static const size_t TASK_STACK_SIZE = 4096;
25static const int16_t MAX_AUDIO_SAMPLE_VALUE = INT16_MAX;
26static const int16_t MIN_AUDIO_SAMPLE_VALUE = INT16_MIN;
28static const char *
const TAG =
"speaker_mixer";
33static const std::array<int16_t, 51> DECIBEL_REDUCTION_TABLE = {
34 32767, 29201, 26022, 23189, 20665, 18415, 16410, 14624, 13032, 11613, 10349, 9222, 8218, 7324, 6527, 5816, 5183,
35 4619, 4116, 3668, 3269, 2913, 2596, 2313, 2061, 1837, 1637, 1459, 1300, 1158, 1032, 920, 820, 731,
36 651, 580, 517, 461, 411, 366, 326, 291, 259, 231, 206, 183, 163, 146, 130, 116, 103};
57static inline uint32_t atomic_subtract_clamped(std::atomic<uint32_t> &var, uint32_t amount) {
58 uint32_t current = var.load(std::memory_order_acquire);
59 uint32_t subtracted = 0;
63 subtracted = std::min(amount, current);
64 new_value = current - subtracted;
65 }
while (!var.compare_exchange_weak(current, new_value, std::memory_order_release, std::memory_order_acquire));
70static bool create_event_group(EventGroupHandle_t &event_group, Component *
component) {
71 event_group = xEventGroupCreate();
72 if (event_group ==
nullptr) {
73 ESP_LOGE(TAG,
"Failed to create event group");
82 "Mixer Source Speaker\n"
83 " Buffer Duration: %" PRIu32
" ms",
88 ESP_LOGCONFIG(TAG,
" Timeout: never");
103 uint32_t remaining_frames = new_frames - delay_to_drain;
106 if (remaining_frames > 0) {
108 if (speakers_playback_frames > 0) {
116 uint32_t event_bits = xEventGroupGetBits(this->
event_group_);
132 xEventGroupClearBits(this->
event_group_, SOURCE_SPEAKER_COMMAND_FINISH);
136 xEventGroupClearBits(this->
event_group_, SOURCE_SPEAKER_COMMAND_FINISH);
141 xEventGroupClearBits(this->
event_group_, SOURCE_SPEAKER_COMMAND_START);
145 xEventGroupClearBits(this->
event_group_, SOURCE_SPEAKER_COMMAND_START);
152 esp_err_t err = this->
start_();
166 case ESP_ERR_NOT_SUPPORTED:
169 case ESP_ERR_INVALID_ARG:
172 case ESP_ERR_INVALID_STATE:
186 (this->pending_playback_frames_.load(std::memory_order_acquire) == 0)) {
189 this->stop_gracefully_) {
203 (this->pending_playback_frames_.load(std::memory_order_acquire) == 0)) {
228 size_t bytes_written = 0;
229 std::shared_ptr<RingBuffer> temp_ring_buffer = this->
ring_buffer_.lock();
230 if (temp_ring_buffer.use_count() > 0) {
232 bytes_written = temp_ring_buffer->write_without_replacement(data,
length, ticks_to_wait);
233 if (bytes_written > 0) {
238 vTaskDelay(ticks_to_wait);
240 return bytes_written;
245 uint32_t event_bits = xEventGroupGetBits(this->
event_group_);
246 if (!(event_bits & command_bit)) {
248#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE)
265 return ESP_ERR_NO_MEM;
268 std::shared_ptr<RingBuffer> temp_ring_buffer = this->
ring_buffer_.lock();
269 if (!temp_ring_buffer) {
274 if (!temp_ring_buffer) {
275 return ESP_ERR_NO_MEM;
289 return ((this->
transfer_buffer_.use_count() > 0) && this->transfer_buffer_->has_buffered_data());
307 TickType_t ticks_to_wait) {
309 const size_t current_length = transfer_buffer->available();
311 size_t bytes_read = transfer_buffer->transfer_data_from_source(ticks_to_wait);
314 if (samples_to_duck > 0) {
315 int16_t *current_buffer =
reinterpret_cast<int16_t *
>(transfer_buffer->get_buffer_start() + current_length);
334 uint8_t total_ducking_steps = 0;
344 if ((duration > 0) && (total_ducking_steps > 0)) {
360 int8_t *current_ducking_db_reduction, uint32_t *ducking_transition_samples_remaining,
361 uint32_t samples_per_ducking_step, int8_t db_change_per_ducking_step) {
362 if (*ducking_transition_samples_remaining > 0) {
366 uint32_t ducking_steps_in_batch =
367 input_samples_to_duck / samples_per_ducking_step + (input_samples_to_duck % samples_per_ducking_step != 0);
369 for (uint32_t i = 0; i < ducking_steps_in_batch; ++i) {
370 uint32_t samples_left_in_step = *ducking_transition_samples_remaining % samples_per_ducking_step;
372 if (samples_left_in_step == 0) {
373 samples_left_in_step = samples_per_ducking_step;
376 uint32_t samples_to_duck = std::min(input_samples_to_duck, samples_left_in_step);
377 samples_to_duck = std::min(samples_to_duck, *ducking_transition_samples_remaining);
380 uint8_t safe_db_reduction_index =
381 clamp<uint8_t>(*current_ducking_db_reduction, 0, DECIBEL_REDUCTION_TABLE.size() - 1);
382 int16_t q15_scale_factor = DECIBEL_REDUCTION_TABLE[safe_db_reduction_index];
386 if (samples_left_in_step - samples_to_duck == 0) {
388 *current_ducking_db_reduction += db_change_per_ducking_step;
391 input_buffer += samples_to_duck;
392 *ducking_transition_samples_remaining -= samples_to_duck;
393 input_samples_to_duck -= samples_to_duck;
397 if ((*current_ducking_db_reduction > 0) && (input_samples_to_duck > 0)) {
400 uint8_t safe_db_reduction_index =
401 clamp<uint8_t>(*current_ducking_db_reduction, 0, DECIBEL_REDUCTION_TABLE.size() - 1);
402 int16_t q15_scale_factor = DECIBEL_REDUCTION_TABLE[safe_db_reduction_index];
417 " Number of output channels: %u",
436 uint32_t event_group_bits = xEventGroupGetBits(this->
event_group_);
445 xEventGroupClearBits(this->
event_group_, MIXER_TASK_COMMAND_START);
448 ESP_LOGE(TAG,
"Failed to start; retrying in 1 second");
451 case ESP_ERR_INVALID_STATE:
452 ESP_LOGE(TAG,
"Failed to start; retrying in 1 second");
456 ESP_LOGE(TAG,
"Failed to start; retrying in 1 second");
464 ESP_LOGD(TAG,
"Starting");
465 xEventGroupClearBits(this->
event_group_, MIXER_TASK_STATE_STARTING);
469 xEventGroupClearBits(this->
event_group_, MIXER_TASK_ERR_ESP_NO_MEM);
472 ESP_LOGV(TAG,
"Started");
474 xEventGroupClearBits(this->
event_group_, MIXER_TASK_STATE_RUNNING);
477 ESP_LOGV(TAG,
"Stopping");
478 xEventGroupClearBits(this->
event_group_, MIXER_TASK_STATE_STOPPING);
482 ESP_LOGD(TAG,
"Stopped");
490 bool all_stopped =
true;
493 all_stopped &= speaker->is_stopped();
502 event_group_bits = xEventGroupGetBits(this->
event_group_);
503 if (event_group_bits == 0) {
514 return ESP_ERR_NOT_SUPPORTED;
523 return ESP_ERR_INVALID_ARG;
529 uint32_t event_bits = xEventGroupGetBits(this->
event_group_);
532 xEventGroupSetBits(this->
event_group_, MIXER_TASK_COMMAND_START);
533#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE)
553 return ESP_ERR_NO_MEM;
562 return ESP_ERR_INVALID_STATE;
589 return ESP_ERR_INVALID_STATE;
597 uint32_t frames_to_transfer) {
598 uint8_t input_channels = input_stream_info.
get_channels();
599 uint8_t output_channels = output_stream_info.
get_channels();
600 const uint8_t max_input_channel_index = input_channels - 1;
602 if (input_channels == output_channels) {
603 size_t bytes_to_copy = input_stream_info.
frames_to_bytes(frames_to_transfer);
604 memcpy(output_buffer, input_buffer, bytes_to_copy);
609 for (uint32_t frame_index = 0; frame_index < frames_to_transfer; ++frame_index) {
610 for (uint8_t output_channel_index = 0; output_channel_index < output_channels; ++output_channel_index) {
611 uint8_t input_channel_index = std::min(output_channel_index, max_input_channel_index);
612 output_buffer[output_channels * frame_index + output_channel_index] =
613 input_buffer[input_channels * frame_index + input_channel_index];
621 uint32_t frames_to_mix) {
622 const uint8_t primary_channels = primary_stream_info.
get_channels();
623 const uint8_t secondary_channels = secondary_stream_info.
get_channels();
624 const uint8_t output_channels = output_stream_info.
get_channels();
626 const uint8_t max_primary_channel_index = primary_channels - 1;
627 const uint8_t max_secondary_channel_index = secondary_channels - 1;
629 for (uint32_t frames_index = 0; frames_index < frames_to_mix; ++frames_index) {
630 for (uint8_t output_channel_index = 0; output_channel_index < output_channels; ++output_channel_index) {
631 const uint32_t secondary_channel_index = std::min(output_channel_index, max_secondary_channel_index);
632 const int32_t secondary_sample = secondary_buffer[frames_index * secondary_channels + secondary_channel_index];
634 const uint32_t primary_channel_index = std::min(output_channel_index, max_primary_channel_index);
635 const int32_t primary_sample =
636 static_cast<int32_t
>(primary_buffer[frames_index * primary_channels + primary_channel_index]);
638 const int32_t added_sample = secondary_sample + primary_sample;
640 output_buffer[frames_index * output_channels + output_channel_index] =
641 static_cast<int16_t
>(clamp<int32_t>(added_sample, MIN_AUDIO_SAMPLE_VALUE, MAX_AUDIO_SAMPLE_VALUE));
654 if (output_transfer_buffer ==
nullptr) {
657 vTaskSuspend(
nullptr);
664 bool sent_finished =
false;
673 uint32_t event_group_bits = xEventGroupGetBits(this_mixer->
event_group_);
679 output_transfer_buffer->transfer_data_to_sink(pdMS_TO_TICKS(TASK_DELAY_MS),
false);
681 const uint32_t output_frames_free =
682 this_mixer->
audio_stream_info_.value().bytes_to_frames(output_transfer_buffer->free());
684 speakers_with_data.
clear();
685 transfer_buffers_with_data.
clear();
688 if (speaker->is_running() && !speaker->get_pause_state()) {
690 std::shared_ptr<audio::AudioSourceTransferBuffer> transfer_buffer = speaker->get_transfer_buffer().lock();
691 if (transfer_buffer.use_count() == 0) {
695 speaker->process_data_from_source(transfer_buffer, 0);
697 if (transfer_buffer->available() > 0) {
699 transfer_buffers_with_data.
push_back(transfer_buffer);
705 if (transfer_buffers_with_data.
empty()) {
707 delay(TASK_DELAY_MS);
711 uint32_t frames_to_mix = output_frames_free;
713 if ((transfer_buffers_with_data.
size() == 1) || this_mixer->
queue_mode_) {
722 const uint32_t frames_available_in_buffer =
723 active_stream_info.
bytes_to_frames(transfer_buffers_with_data[0]->available());
724 frames_to_mix = std::min(frames_to_mix, frames_available_in_buffer);
725 copy_frames(
reinterpret_cast<int16_t *
>(transfer_buffers_with_data[0]->get_buffer_start()), active_stream_info,
726 reinterpret_cast<int16_t *
>(output_transfer_buffer->get_buffer_end()),
730 if (!speakers_with_data[0]->has_contributed_.load(std::memory_order_acquire)) {
731 speakers_with_data[0]->playback_delay_frames_.store(
732 this_mixer->
frames_in_pipeline_.load(std::memory_order_acquire), std::memory_order_release);
733 speakers_with_data[0]->has_contributed_.store(
true, std::memory_order_release);
737 speakers_with_data[0]->pending_playback_frames_.fetch_add(frames_to_mix, std::memory_order_release);
738 transfer_buffers_with_data[0]->decrease_buffer_length(active_stream_info.
frames_to_bytes(frames_to_mix));
741 output_transfer_buffer->increase_buffer_length(
747 if (!sent_finished) {
749 sent_finished =
true;
760 sent_finished =
false;
765 for (
size_t i = 0; i < transfer_buffers_with_data.
size(); ++i) {
766 const uint32_t frames_available_in_buffer =
767 speakers_with_data[i]->get_audio_stream_info().bytes_to_frames(transfer_buffers_with_data[i]->available());
768 frames_to_mix = std::min(frames_to_mix, frames_available_in_buffer);
770 int16_t *primary_buffer =
reinterpret_cast<int16_t *
>(transfer_buffers_with_data[0]->get_buffer_start());
774 for (
size_t i = 1; i < transfer_buffers_with_data.
size(); ++i) {
776 reinterpret_cast<int16_t *
>(transfer_buffers_with_data[i]->get_buffer_start()),
777 speakers_with_data[i]->get_audio_stream_info(),
778 reinterpret_cast<int16_t *
>(output_transfer_buffer->get_buffer_end()),
781 if (i != transfer_buffers_with_data.
size() - 1) {
783 primary_buffer =
reinterpret_cast<int16_t *
>(output_transfer_buffer->get_buffer_end());
789 uint32_t current_pipeline_frames = this_mixer->
frames_in_pipeline_.load(std::memory_order_acquire);
792 for (
size_t i = 0; i < transfer_buffers_with_data.
size(); ++i) {
794 if (!speakers_with_data[i]->has_contributed_.load(std::memory_order_acquire)) {
795 speakers_with_data[i]->playback_delay_frames_.store(current_pipeline_frames, std::memory_order_release);
796 speakers_with_data[i]->has_contributed_.store(
true, std::memory_order_release);
799 speakers_with_data[i]->pending_playback_frames_.fetch_add(frames_to_mix, std::memory_order_release);
800 transfer_buffers_with_data[i]->decrease_buffer_length(
801 speakers_with_data[i]->get_audio_stream_info().frames_to_bytes(frames_to_mix));
805 output_transfer_buffer->increase_buffer_length(
816 output_transfer_buffer.reset();
820 vTaskSuspend(
nullptr);
void wake_loop_threadsafe()
Wake the main event loop from a FreeRTOS task Thread-safe, can be called from task context to immedia...
void status_momentary_error(const char *name, uint32_t length=5000)
Set error status flag and automatically clear it after a timeout.
void status_clear_error()
void enable_loop_soon_any_context()
Thread and ISR-safe version of enable_loop() that can be called from any context.
bool status_has_error() const
void disable_loop()
Disable this component's loop.
Fixed-capacity vector - allocates once at runtime, never reallocates This avoids std::vector template...
void push_back(const T &value)
Add element without bounds checking Caller must ensure sufficient capacity was allocated via init() S...
An STL allocator that uses SPI or internal RAM.
void deallocate(T *p, size_t n)
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.
size_t frames_to_bytes(uint32_t frames) const
Converts frames to bytes.
uint8_t get_bits_per_sample() const
uint32_t ms_to_samples(uint32_t ms) const
Converts duration to samples.
uint32_t bytes_to_frames(size_t bytes) const
Convert bytes to frames.
uint8_t get_channels() const
uint32_t get_sample_rate() const
uint32_t bytes_to_samples(size_t bytes) const
Convert bytes to samples.
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.
void dump_config() override
esp_err_t start(audio::AudioStreamInfo &stream_info)
Starts the mixer task.
FixedVector< SourceSpeaker * > source_speakers_
speaker::Speaker * get_output_speaker() const
StackType_t * task_stack_buffer_
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.
bool task_stack_in_psram_
TaskHandle_t task_handle_
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)
EventGroupHandle_t event_group_
speaker::Speaker * output_speaker_
optional< audio::AudioStreamInfo > audio_stream_info_
uint32_t buffer_duration_ms_
std::shared_ptr< audio::AudioSourceTransferBuffer > transfer_buffer_
optional< uint32_t > timeout_ms_
float get_volume() override
void set_mute_state(bool mute_state) override
Mute state changes are passed to the parent's output speaker.
int8_t current_ducking_db_reduction_
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_mute_state() override
uint32_t samples_per_ducking_step_
std::atomic< uint32_t > playback_delay_frames_
uint32_t stopping_start_ms_
void apply_ducking(uint8_t decibel_reduction, uint32_t duration)
Sets the ducking level for the source speaker.
std::weak_ptr< RingBuffer > ring_buffer_
int8_t target_ducking_db_reduction_
size_t play(const uint8_t *data, size_t length, TickType_t ticks_to_wait) override
uint32_t ducking_transition_samples_remaining_
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.
std::atomic< bool > has_contributed_
void dump_config() override
void enter_stopping_state_()
int8_t db_change_per_ducking_step_
void set_volume(float volume) override
Volume state changes are passed to the parent's output speaker.
bool has_buffered_data() const override
uint32_t last_seen_data_ms_
EventGroupHandle_t event_group_
std::atomic< uint32_t > pending_playback_frames_
value_type const & value() const
virtual void set_volume(float volume)
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...
virtual float get_volume()
virtual bool get_pause_state() const
CallbackManager< void(uint32_t, int64_t)> audio_output_callback_
void set_audio_stream_info(const audio::AudioStreamInfo &audio_stream_info)
audio::AudioStreamInfo & get_audio_stream_info()
virtual bool get_mute_state()
virtual void set_mute_state(bool mute_state)
audio::AudioStreamInfo audio_stream_info_
const Component * component
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.
@ MIXER_TASK_STATE_STOPPED
@ MIXER_TASK_STATE_RUNNING
@ MIXER_TASK_COMMAND_STOP
@ MIXER_TASK_STATE_STARTING
@ MIXER_TASK_STATE_STOPPING
@ MIXER_TASK_ERR_ESP_NO_MEM
@ MIXER_TASK_COMMAND_START
@ SOURCE_SPEAKER_COMMAND_STOP
@ SOURCE_SPEAKER_COMMAND_FINISH
@ SOURCE_SPEAKER_COMMAND_START
Providing packet encoding functions for exchanging data with a remote host.
void IRAM_ATTR HOT delay(uint32_t ms)
uint32_t IRAM_ATTR HOT millis()
Application App
Global storage of Application pointer - only one Application can exist.