11#include <pcm_convert.h>
18static const UBaseType_t MIXER_TASK_PRIORITY = 10;
20static const uint32_t STOPPING_TIMEOUT_MS = 5000;
21static const uint32_t TRANSFER_BUFFER_DURATION_MS = 50;
22static const uint32_t TASK_DELAY_MS = 25;
23static const uint32_t MIXER_AUTO_STOP_DEBOUNCE_MS = 200;
25static const size_t TASK_STACK_SIZE = 4096;
27static const char *
const TAG =
"speaker_mixer";
48static inline uint32_t atomic_subtract_clamped(std::atomic<uint32_t> &var,
uint32_t amount) {
49 uint32_t current = var.load(std::memory_order_acquire);
54 subtracted = std::min(amount, current);
55 new_value = current - subtracted;
56 }
while (!var.compare_exchange_weak(current, new_value, std::memory_order_release, std::memory_order_acquire));
61static bool create_event_group(EventGroupHandle_t &event_group, Component *
component) {
62 event_group = xEventGroupCreate();
63 if (event_group ==
nullptr) {
64 ESP_LOGE(TAG,
"Failed to create event group");
73 "Mixer Source Speaker\n"
74 " Buffer Duration: %" PRIu32
" ms",
77 ESP_LOGCONFIG(TAG,
" Timeout: %" PRIu32
" ms", this->
timeout_ms_.value());
79 ESP_LOGCONFIG(TAG,
" Timeout: never");
94 uint32_t remaining_frames = new_frames - delay_to_drain;
97 if (remaining_frames > 0) {
99 if (speakers_playback_frames > 0) {
123 xEventGroupClearBits(this->
event_group_, SOURCE_SPEAKER_COMMAND_FINISH);
127 xEventGroupClearBits(this->
event_group_, SOURCE_SPEAKER_COMMAND_FINISH);
132 xEventGroupClearBits(this->
event_group_, SOURCE_SPEAKER_COMMAND_START);
136 xEventGroupClearBits(this->
event_group_, SOURCE_SPEAKER_COMMAND_START);
143 esp_err_t err = this->
start_();
157 case ESP_ERR_NOT_SUPPORTED:
160 case ESP_ERR_INVALID_ARG:
163 case ESP_ERR_INVALID_STATE:
177 (this->pending_playback_frames_.load(std::memory_order_acquire) == 0)) {
180 this->stop_gracefully_) {
194 (this->pending_playback_frames_.load(std::memory_order_acquire) == 0)) {
219 size_t bytes_written = 0;
220 std::shared_ptr<ring_buffer::RingBuffer> temp_ring_buffer = this->
ring_buffer_.lock();
221 if (temp_ring_buffer.use_count() > 0) {
223 bytes_written = temp_ring_buffer->write_without_replacement(data,
length, ticks_to_wait);
224 if (bytes_written > 0) {
229 vTaskDelay(ticks_to_wait);
231 return bytes_written;
237 if (!(event_bits & command_bit)) {
251 const size_t ring_buffer_size =
254 std::shared_ptr<ring_buffer::RingBuffer> temp_ring_buffer = this->
ring_buffer_.lock();
255 if (!temp_ring_buffer) {
260 if (!temp_ring_buffer) {
261 return ESP_ERR_NO_MEM;
266 static_cast<uint8_t
>(bytes_per_frame));
267 if (source ==
nullptr) {
268 return ESP_ERR_NO_MEM;
281 return ((this->
audio_source_.use_count() > 0) && this->audio_source_->has_buffered_data());
299 TickType_t ticks_to_wait) {
300 if (audio_source->available() > 0) {
305 size_t bytes_read = audio_source->fill(ticks_to_wait,
false);
308 if (samples_to_duck > 0) {
309 esp_audio_libs::ducking::apply(audio_source->mutable_data(),
310 static_cast<uint8_t
>(this->audio_stream_info_.get_bits_per_sample() / 8),
311 samples_to_duck, this->ducking_state_);
319 esp_audio_libs::ducking::set_target(this->
ducking_state_, decibel_reduction, transition_samples);
331 " Number of output channels: %" PRIu8
"\n"
332 " Output bits per sample: %" PRIu8,
358 this->task_stack_in_psram_)) {
359 xEventGroupClearBits(this->
event_group_, MIXER_TASK_COMMAND_START);
361 ESP_LOGE(TAG,
"Failed to start; retrying in 1 second");
369 ESP_LOGD(TAG,
"Starting");
370 xEventGroupClearBits(this->
event_group_, MIXER_TASK_STATE_STARTING);
374 xEventGroupClearBits(this->
event_group_, MIXER_TASK_ERR_ESP_NO_MEM);
377 ESP_LOGV(TAG,
"Started");
379 xEventGroupClearBits(this->
event_group_, MIXER_TASK_STATE_RUNNING);
382 ESP_LOGV(TAG,
"Stopping");
383 xEventGroupClearBits(this->
event_group_, MIXER_TASK_STATE_STOPPING);
387 ESP_LOGD(TAG,
"Stopped");
395 bool all_stopped =
true;
398 all_stopped &= speaker->is_stopped();
412 xEventGroupClearBits(this->
event_group_, MIXER_TASK_COMMAND_STOP);
417 event_group_bits = xEventGroupGetBits(this->
event_group_);
418 if (event_group_bits == 0) {
433 return ESP_ERR_INVALID_ARG;
445 xEventGroupSetBits(this->
event_group_, MIXER_TASK_COMMAND_START);
462 if (output_transfer_buffer ==
nullptr) {
465 vTaskSuspend(
nullptr);
472 bool sent_finished =
false;
487 output_transfer_buffer->transfer_data_to_sink(pdMS_TO_TICKS(TASK_DELAY_MS),
false);
490 this_mixer->
audio_stream_info_.value().bytes_to_frames(output_transfer_buffer->free());
492 speakers_with_data.
clear();
493 audio_sources_with_data.
clear();
496 if (speaker->is_running() && !speaker->get_pause_state()) {
498 std::shared_ptr<audio::RingBufferAudioSource> audio_source = speaker->get_audio_source().lock();
499 if (audio_source.use_count() == 0) {
503 speaker->process_data_from_source(audio_source, 0);
505 if (audio_source->available() > 0) {
507 audio_sources_with_data.
push_back(audio_source);
513 if (audio_sources_with_data.
empty()) {
515 delay(TASK_DELAY_MS);
519 uint32_t frames_to_mix = output_frames_free;
523 const uint8_t output_channels = output_info.
get_channels();
525 if ((audio_sources_with_data.
size() == 1) || this_mixer->
queue_mode_) {
534 const uint32_t frames_available_in_buffer =
535 active_stream_info.
bytes_to_frames(audio_sources_with_data[0]->available());
536 frames_to_mix = std::min(frames_to_mix, frames_available_in_buffer);
537 esp_audio_libs::pcm_convert::copy_frames(
538 audio_sources_with_data[0]->data(), output_transfer_buffer->get_buffer_end(),
540 output_bps, output_channels, frames_to_mix);
543 if (!speakers_with_data[0]->has_contributed_.load(std::memory_order_acquire)) {
544 speakers_with_data[0]->playback_delay_frames_.store(
545 this_mixer->
frames_in_pipeline_.load(std::memory_order_acquire), std::memory_order_release);
546 speakers_with_data[0]->has_contributed_.store(
true, std::memory_order_release);
550 speakers_with_data[0]->pending_playback_frames_.fetch_add(frames_to_mix, std::memory_order_release);
551 audio_sources_with_data[0]->consume(active_stream_info.
frames_to_bytes(frames_to_mix));
554 output_transfer_buffer->increase_buffer_length(output_info.
frames_to_bytes(frames_to_mix));
559 if (!sent_finished) {
561 sent_finished =
true;
572 sent_finished =
false;
577 for (
size_t i = 0; i < audio_sources_with_data.
size(); ++i) {
578 const uint32_t frames_available_in_buffer =
579 speakers_with_data[i]->get_audio_stream_info().bytes_to_frames(audio_sources_with_data[i]->available());
580 frames_to_mix = std::min(frames_to_mix, frames_available_in_buffer);
582 const uint8_t *primary_buffer = audio_sources_with_data[0]->data();
586 for (
size_t i = 1; i < audio_sources_with_data.
size(); ++i) {
587 esp_audio_libs::mixer::mix_frames(
589 primary_stream_info.
get_channels(), audio_sources_with_data[i]->data(),
590 static_cast<uint8_t
>(speakers_with_data[i]->get_audio_stream_info().get_bits_per_sample() / 8),
591 speakers_with_data[i]->get_audio_stream_info().get_channels(), output_transfer_buffer->get_buffer_end(),
592 output_bps, output_channels, frames_to_mix);
594 if (i != audio_sources_with_data.
size() - 1) {
596 primary_buffer = output_transfer_buffer->get_buffer_end();
597 primary_stream_info = output_info;
605 for (
size_t i = 0; i < audio_sources_with_data.
size(); ++i) {
607 if (!speakers_with_data[i]->has_contributed_.load(std::memory_order_acquire)) {
608 speakers_with_data[i]->playback_delay_frames_.store(current_pipeline_frames, std::memory_order_release);
609 speakers_with_data[i]->has_contributed_.store(
true, std::memory_order_release);
612 speakers_with_data[i]->pending_playback_frames_.fetch_add(frames_to_mix, std::memory_order_release);
613 audio_sources_with_data[i]->consume(
614 speakers_with_data[i]->get_audio_stream_info().frames_to_bytes(frames_to_mix));
618 output_transfer_buffer->increase_buffer_length(output_info.
frames_to_bytes(frames_to_mix));
631 vTaskSuspend(
nullptr);
void wake_loop_threadsafe()
Wake the main event loop from another thread or callback.
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...
bool create(TaskFunction_t fn, const char *name, uint32_t stack_size, void *param, UBaseType_t priority, bool use_psram)
Allocate stack and create task.
bool is_created() const
Check if the task has been created and not yet destroyed.
void deallocate()
Delete the task (if running) and free the stack buffer.
static std::unique_ptr< AudioSinkTransferBuffer > create(size_t buffer_size)
Creates a new sink 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.
static std::unique_ptr< RingBufferAudioSource > create(std::shared_ptr< ring_buffer::RingBuffer > ring_buffer, size_t max_fill_bytes, uint8_t alignment_bytes=1)
Creates a new ring-buffer-backed audio source after validating its parameters.
uint32_t all_stopped_since_ms_
void dump_config() override
esp_err_t start(audio::AudioStreamInfo &stream_info)
Starts the mixer task.
uint8_t output_bits_per_sample_
FixedVector< SourceSpeaker * > source_speakers_
speaker::Speaker * get_output_speaker() const
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_
esp_audio_libs::ducking::DuckingState ducking_state_
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.
void send_command_(uint32_t command_bit, bool wake_loop=false)
bool get_mute_state() override
std::shared_ptr< audio::RingBufferAudioSource > audio_source_
std::atomic< uint32_t > playback_delay_frames_
std::weak_ptr< ring_buffer::RingBuffer > ring_buffer_
uint32_t stopping_start_ms_
void apply_ducking(uint8_t decibel_reduction, uint32_t duration)
Sets the ducking level for the source speaker.
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.
std::atomic< bool > has_contributed_
void dump_config() override
void enter_stopping_state_()
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_
static std::unique_ptr< RingBuffer > create(size_t len, MemoryPreference preference=MemoryPreference::EXTERNAL_FIRST)
virtual void set_volume(float volume)
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()
void add_audio_output_callback(F &&callback)
Callback function for sending the duration of the audio written to the speaker since the last callbac...
virtual bool get_mute_state()
virtual void set_mute_state(bool mute_state)
audio::AudioStreamInfo audio_stream_info_
const Component * component
@ 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
void HOT delay(uint32_t ms)
uint32_t IRAM_ATTR HOT millis()
Application App
Global storage of Application pointer - only one Application can exist.