esphome 2024.12.4__py3-none-any.whl → 2025.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- esphome/__main__.py +16 -3
- esphome/components/adc/__init__.py +17 -11
- esphome/components/adc/adc_sensor.h +17 -0
- esphome/components/adc/adc_sensor_common.cpp +55 -0
- esphome/components/adc/adc_sensor_esp32.cpp +8 -5
- esphome/components/adc/adc_sensor_esp8266.cpp +10 -6
- esphome/components/adc/adc_sensor_libretiny.cpp +11 -6
- esphome/components/adc/adc_sensor_rp2040.cpp +13 -10
- esphome/components/adc/sensor.py +9 -3
- esphome/components/ads1115/ads1115.cpp +56 -7
- esphome/components/ads1115/ads1115.h +13 -1
- esphome/components/ads1115/sensor/__init__.py +16 -0
- esphome/components/ads1115/sensor/ads1115_sensor.cpp +2 -1
- esphome/components/ads1115/sensor/ads1115_sensor.h +2 -0
- esphome/components/animation/__init__.py +23 -261
- esphome/components/animation/animation.cpp +2 -2
- esphome/components/animation/animation.h +2 -1
- esphome/components/api/api_pb2.cpp +14 -0
- esphome/components/api/api_pb2.h +1 -0
- esphome/components/api/client.py +8 -3
- esphome/components/audio/__init__.py +112 -0
- esphome/components/audio/audio.cpp +67 -0
- esphome/components/audio/audio.h +125 -7
- esphome/components/audio/audio_decoder.cpp +361 -0
- esphome/components/audio/audio_decoder.h +135 -0
- esphome/components/audio/audio_reader.cpp +308 -0
- esphome/components/audio/audio_reader.h +85 -0
- esphome/components/audio/audio_resampler.cpp +159 -0
- esphome/components/audio/audio_resampler.h +101 -0
- esphome/components/audio/audio_transfer_buffer.cpp +165 -0
- esphome/components/audio/audio_transfer_buffer.h +139 -0
- esphome/components/audio_adc/__init__.py +41 -0
- esphome/components/audio_adc/audio_adc.h +17 -0
- esphome/components/audio_adc/automation.h +23 -0
- esphome/components/bk72xx/__init__.py +1 -0
- esphome/components/ble_client/ble_client.cpp +1 -2
- esphome/components/ble_client/sensor/__init__.py +1 -1
- esphome/components/ble_client/text_sensor/__init__.py +1 -1
- esphome/components/bluetooth_proxy/bluetooth_connection.cpp +5 -0
- esphome/components/bluetooth_proxy/bluetooth_connection.h +1 -0
- esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +5 -0
- esphome/components/ch422g/ch422g.h +2 -0
- esphome/components/climate/__init__.py +1 -1
- esphome/components/climate_ir/climate_ir.cpp +2 -1
- esphome/components/coolix/coolix.cpp +2 -1
- esphome/components/cse7766/cse7766.cpp +8 -16
- esphome/components/custom/__init__.py +0 -3
- esphome/components/custom/binary_sensor/__init__.py +2 -28
- esphome/components/custom/climate/__init__.py +2 -27
- esphome/components/custom/cover/__init__.py +2 -27
- esphome/components/custom/light/__init__.py +2 -27
- esphome/components/custom/output/__init__.py +2 -58
- esphome/components/custom/sensor/__init__.py +2 -24
- esphome/components/custom/switch/__init__.py +2 -24
- esphome/components/custom/text_sensor/__init__.py +2 -29
- esphome/components/custom_component/__init__.py +3 -27
- esphome/components/daly_bms/daly_bms.cpp +6 -0
- esphome/components/daly_bms/daly_bms.h +2 -0
- esphome/components/daly_bms/sensor.py +6 -0
- esphome/components/debug/debug_component.cpp +4 -0
- esphome/components/debug/debug_component.h +14 -0
- esphome/components/debug/debug_esp32.cpp +154 -74
- esphome/components/dfplayer/dfplayer.cpp +15 -2
- esphome/components/dfrobot_sen0395/dfrobot_sen0395.cpp +2 -1
- esphome/components/dht/dht.cpp +4 -2
- esphome/components/dht/sensor.py +1 -1
- esphome/components/display/__init__.py +18 -5
- esphome/components/display/display.cpp +16 -3
- esphome/components/display/rect.cpp +2 -1
- esphome/components/es7210/__init__.py +0 -0
- esphome/components/es7210/audio_adc.py +51 -0
- esphome/components/es7210/es7210.cpp +228 -0
- esphome/components/es7210/es7210.h +62 -0
- esphome/components/es7210/es7210_const.h +129 -0
- esphome/components/es7243e/__init__.py +0 -0
- esphome/components/es7243e/audio_adc.py +34 -0
- esphome/components/es7243e/es7243e.cpp +125 -0
- esphome/components/es7243e/es7243e.h +37 -0
- esphome/components/es7243e/es7243e_const.h +54 -0
- esphome/components/es8156/__init__.py +0 -0
- esphome/components/es8156/audio_dac.py +27 -0
- esphome/components/es8156/es8156.cpp +87 -0
- esphome/components/es8156/es8156.h +51 -0
- esphome/components/es8156/es8156_const.h +68 -0
- esphome/components/es8311/audio_dac.py +1 -2
- esphome/components/esp32/__init__.py +1 -0
- esphome/components/esp32/core.cpp +5 -1
- esphome/components/esp32/gpio.h +2 -0
- esphome/components/esp32_ble/__init__.py +39 -0
- esphome/components/esp32_ble/queue.h +4 -4
- esphome/components/esp32_ble_client/ble_client_base.cpp +46 -0
- esphome/components/esp32_ble_client/ble_client_base.h +2 -0
- esphome/components/esp32_ble_server/__init__.py +582 -12
- esphome/components/esp32_ble_server/ble_characteristic.cpp +48 -60
- esphome/components/esp32_ble_server/ble_characteristic.h +24 -17
- esphome/components/esp32_ble_server/ble_descriptor.cpp +21 -9
- esphome/components/esp32_ble_server/ble_descriptor.h +17 -6
- esphome/components/esp32_ble_server/ble_server.cpp +62 -67
- esphome/components/esp32_ble_server/ble_server.h +28 -32
- esphome/components/esp32_ble_server/ble_server_automations.cpp +77 -0
- esphome/components/esp32_ble_server/ble_server_automations.h +115 -0
- esphome/components/esp32_ble_server/ble_service.cpp +17 -15
- esphome/components/esp32_ble_server/ble_service.h +10 -14
- esphome/components/esp32_ble_tracker/__init__.py +6 -39
- esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +33 -10
- esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +8 -4
- esphome/components/esp32_dac/esp32_dac.cpp +16 -7
- esphome/components/esp32_dac/esp32_dac.h +8 -0
- esphome/components/esp32_dac/output.py +16 -4
- esphome/components/esp32_improv/__init__.py +2 -8
- esphome/components/esp32_improv/esp32_improv_component.cpp +21 -20
- esphome/components/esp32_improv/esp32_improv_component.h +3 -4
- esphome/components/esp32_rmt/__init__.py +28 -3
- esphome/components/esp32_rmt_led_strip/led_strip.cpp +73 -6
- esphome/components/esp32_rmt_led_strip/led_strip.h +21 -3
- esphome/components/esp32_rmt_led_strip/light.py +72 -7
- esphome/components/esp32_touch/esp32_touch.cpp +5 -0
- esphome/components/esp8266/__init__.py +1 -0
- esphome/components/esp8266/gpio.h +1 -0
- esphome/components/ethernet/__init__.py +10 -10
- esphome/components/event/event.cpp +4 -2
- esphome/components/event/event.h +2 -0
- esphome/components/event_emitter/__init__.py +5 -0
- esphome/components/event_emitter/event_emitter.cpp +14 -0
- esphome/components/event_emitter/event_emitter.h +63 -0
- esphome/components/font/__init__.py +1 -1
- esphome/components/gcja5/gcja5.cpp +2 -1
- esphome/components/graph/graph.cpp +4 -9
- esphome/components/haier/haier_base.cpp +2 -1
- esphome/components/haier/hon_climate.cpp +2 -1
- esphome/components/heatpumpir/heatpumpir.cpp +2 -1
- esphome/components/host/__init__.py +1 -0
- esphome/components/host/gpio.h +1 -0
- esphome/components/http_request/http_request.h +2 -2
- esphome/components/http_request/http_request_arduino.cpp +1 -1
- esphome/components/http_request/http_request_idf.cpp +1 -1
- esphome/components/i2c/i2c_bus_esp_idf.cpp +4 -0
- esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp +7 -5
- esphome/components/i2s_audio/speaker/__init__.py +53 -6
- esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +92 -46
- esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +8 -0
- esphome/components/ili9xxx/display.py +29 -11
- esphome/components/ili9xxx/ili9xxx_display.cpp +2 -5
- esphome/components/ili9xxx/ili9xxx_display.h +2 -1
- esphome/components/image/__init__.py +443 -255
- esphome/components/image/image.cpp +115 -61
- esphome/components/image/image.h +15 -24
- esphome/components/json/json_util.cpp +8 -34
- esphome/components/libretiny/__init__.py +1 -0
- esphome/components/libretiny/gpio_arduino.h +1 -0
- esphome/components/light/light_color_values.h +1 -1
- esphome/components/logger/__init__.py +45 -9
- esphome/components/logger/logger.cpp +16 -14
- esphome/components/logger/logger.h +11 -7
- esphome/components/logger/select/__init__.py +29 -0
- esphome/components/logger/select/logger_level_select.cpp +27 -0
- esphome/components/logger/select/logger_level_select.h +15 -0
- esphome/components/lvgl/__init__.py +96 -73
- esphome/components/lvgl/automation.py +39 -7
- esphome/components/lvgl/defines.py +8 -2
- esphome/components/lvgl/lvgl_esphome.cpp +8 -15
- esphome/components/lvgl/lvgl_esphome.h +20 -5
- esphome/components/lvgl/schemas.py +25 -14
- esphome/components/lvgl/trigger.py +27 -3
- esphome/components/lvgl/widgets/dropdown.py +1 -1
- esphome/components/lvgl/widgets/keyboard.py +8 -1
- esphome/components/lvgl/widgets/meter.py +2 -1
- esphome/components/lvgl/widgets/msgbox.py +1 -1
- esphome/components/lvgl/widgets/obj.py +1 -12
- esphome/components/lvgl/widgets/page.py +37 -2
- esphome/components/lvgl/widgets/tabview.py +1 -1
- esphome/components/max6956/max6956.h +2 -0
- esphome/components/mcp23016/mcp23016.h +2 -0
- esphome/components/mcp23xxx_base/mcp23xxx_base.h +2 -0
- esphome/components/mdns/__init__.py +1 -1
- esphome/components/media_player/__init__.py +37 -8
- esphome/components/media_player/automation.h +11 -2
- esphome/components/media_player/media_player.cpp +8 -0
- esphome/components/media_player/media_player.h +8 -4
- esphome/components/micronova/switch/micronova_switch.cpp +4 -2
- esphome/components/midea/ac_automations.h +3 -1
- esphome/components/midea/air_conditioner.cpp +7 -5
- esphome/components/midea/air_conditioner.h +1 -1
- esphome/components/midea/climate.py +4 -2
- esphome/components/midea/ir_transmitter.h +36 -5
- esphome/components/mixer/__init__.py +0 -0
- esphome/components/mixer/speaker/__init__.py +172 -0
- esphome/components/mixer/speaker/automation.h +19 -0
- esphome/components/mixer/speaker/mixer_speaker.cpp +624 -0
- esphome/components/mixer/speaker/mixer_speaker.h +207 -0
- esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp +7 -13
- esphome/components/mpr121/mpr121.h +2 -0
- esphome/components/mqtt/__init__.py +1 -1
- esphome/components/mqtt/mqtt_client.cpp +7 -1
- esphome/components/mqtt/mqtt_client.h +1 -1
- esphome/components/mqtt/mqtt_climate.cpp +2 -2
- esphome/components/network/ip_address.h +2 -0
- esphome/components/nextion/automation.h +17 -0
- esphome/components/nextion/display.py +42 -17
- esphome/components/nextion/nextion.cpp +4 -10
- esphome/components/nextion/nextion.h +89 -82
- esphome/components/nextion/nextion_commands.cpp +10 -10
- esphome/components/ntc/sensor.py +2 -4
- esphome/components/online_image/__init__.py +98 -46
- esphome/components/online_image/bmp_image.cpp +101 -0
- esphome/components/online_image/bmp_image.h +40 -0
- esphome/components/online_image/image_decoder.cpp +31 -2
- esphome/components/online_image/image_decoder.h +24 -15
- esphome/components/online_image/jpeg_image.cpp +92 -0
- esphome/components/online_image/jpeg_image.h +34 -0
- esphome/components/online_image/online_image.cpp +118 -58
- esphome/components/online_image/online_image.h +39 -9
- esphome/components/online_image/png_image.cpp +7 -3
- esphome/components/online_image/png_image.h +2 -1
- esphome/components/opentherm/__init__.py +73 -7
- esphome/components/opentherm/automation.h +25 -0
- esphome/components/opentherm/const.py +1 -0
- esphome/components/opentherm/generate.py +39 -6
- esphome/components/opentherm/hub.cpp +117 -79
- esphome/components/opentherm/hub.h +31 -15
- esphome/components/opentherm/opentherm.cpp +47 -23
- esphome/components/opentherm/opentherm.h +27 -6
- esphome/components/opentherm/opentherm_macros.h +11 -0
- esphome/components/opentherm/schema.py +78 -1
- esphome/components/opentherm/validate.py +7 -2
- esphome/components/pca6416a/pca6416a.h +2 -0
- esphome/components/pca9554/pca9554.h +2 -0
- esphome/components/pcf8574/pcf8574.h +2 -0
- esphome/components/preferences/__init__.py +2 -4
- esphome/components/preferences/syncer.h +10 -3
- esphome/components/prometheus/prometheus_handler.cpp +313 -0
- esphome/components/prometheus/prometheus_handler.h +48 -7
- esphome/components/psram/psram.cpp +8 -1
- esphome/components/pulse_counter/pulse_counter_sensor.cpp +14 -9
- esphome/components/pulse_counter/pulse_counter_sensor.h +4 -4
- esphome/components/pulse_meter/pulse_meter_sensor.cpp +2 -0
- esphome/components/qspi_dbi/__init__.py +3 -0
- esphome/components/qspi_dbi/display.py +74 -47
- esphome/components/qspi_dbi/models.py +245 -2
- esphome/components/qspi_dbi/qspi_dbi.cpp +9 -16
- esphome/components/qspi_dbi/qspi_dbi.h +2 -2
- esphome/components/remote_base/__init__.py +77 -25
- esphome/components/remote_base/remote_base.cpp +1 -1
- esphome/components/remote_base/remote_base.h +20 -2
- esphome/components/remote_base/toto_protocol.cpp +100 -0
- esphome/components/remote_base/toto_protocol.h +45 -0
- esphome/components/remote_receiver/__init__.py +55 -10
- esphome/components/remote_receiver/remote_receiver.h +36 -3
- esphome/components/remote_receiver/remote_receiver_esp32.cpp +145 -6
- esphome/components/remote_transmitter/__init__.py +62 -4
- esphome/components/remote_transmitter/remote_transmitter.h +21 -2
- esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +140 -4
- esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp +3 -3
- esphome/components/remote_transmitter/remote_transmitter_libretiny.cpp +3 -3
- esphome/components/resampler/__init__.py +0 -0
- esphome/components/resampler/speaker/__init__.py +103 -0
- esphome/components/resampler/speaker/resampler_speaker.cpp +318 -0
- esphome/components/resampler/speaker/resampler_speaker.h +107 -0
- esphome/components/resistance/resistance_sensor.h +2 -3
- esphome/components/resistance/sensor.py +2 -9
- esphome/components/rotary_encoder/rotary_encoder.cpp +8 -4
- esphome/components/rp2040/__init__.py +1 -0
- esphome/components/rp2040/gpio.h +1 -0
- esphome/components/rtl87xx/__init__.py +2 -0
- esphome/components/scd30/sensor.py +1 -1
- esphome/components/sdl/binary_sensor.py +270 -0
- esphome/components/sdl/sdl_esphome.cpp +16 -0
- esphome/components/sdl/sdl_esphome.h +9 -0
- esphome/components/seeed_mr60bha2/binary_sensor.py +25 -0
- esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp +26 -2
- esphome/components/seeed_mr60bha2/seeed_mr60bha2.h +9 -20
- esphome/components/seeed_mr60bha2/sensor.py +9 -1
- esphome/components/sn74hc165/sn74hc165.h +3 -0
- esphome/components/sn74hc595/sn74hc595.h +3 -0
- esphome/components/speaker/__init__.py +5 -4
- esphome/components/speaker/media_player/__init__.py +458 -0
- esphome/components/speaker/media_player/audio_pipeline.cpp +568 -0
- esphome/components/speaker/media_player/audio_pipeline.h +159 -0
- esphome/components/speaker/media_player/automation.h +26 -0
- esphome/components/speaker/media_player/speaker_media_player.cpp +577 -0
- esphome/components/speaker/media_player/speaker_media_player.h +160 -0
- esphome/components/speaker/speaker.h +20 -0
- esphome/components/spi/__init__.py +1 -5
- esphome/components/spi/spi.cpp +7 -1
- esphome/components/spi/spi.h +21 -2
- esphome/components/spi_led_strip/light.py +3 -5
- esphome/components/spi_led_strip/spi_led_strip.cpp +67 -0
- esphome/components/spi_led_strip/spi_led_strip.h +8 -60
- esphome/components/sprinkler/sprinkler.cpp +3 -1
- esphome/components/sx1509/sx1509_gpio_pin.h +2 -0
- esphome/components/tca9555/tca9555.h +2 -0
- esphome/components/toshiba/toshiba.cpp +2 -1
- esphome/components/tuya/light/tuya_light.cpp +4 -2
- esphome/components/uart/uart_component_esp32_arduino.cpp +2 -2
- esphome/components/uart/uart_component_esp_idf.cpp +2 -2
- esphome/components/udp/__init__.py +8 -2
- esphome/components/udp/udp_component.cpp +25 -56
- esphome/components/udp/udp_component.h +3 -0
- esphome/components/uponor_smatrix/sensor/__init__.py +14 -4
- esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.cpp +5 -0
- esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.h +1 -0
- esphome/components/uptime/text_sensor/__init__.py +19 -0
- esphome/components/uptime/text_sensor/uptime_text_sensor.cpp +63 -0
- esphome/components/uptime/text_sensor/uptime_text_sensor.h +25 -0
- esphome/components/voice_assistant/voice_assistant.cpp +24 -14
- esphome/components/voice_assistant/voice_assistant.h +8 -0
- esphome/components/waveshare_epaper/display.py +22 -1
- esphome/components/waveshare_epaper/waveshare_213v3.cpp +9 -3
- esphome/components/waveshare_epaper/waveshare_epaper.cpp +1155 -44
- esphome/components/waveshare_epaper/waveshare_epaper.h +208 -7
- esphome/components/web_server/web_server.cpp +28 -6
- esphome/components/weikai/weikai.h +2 -0
- esphome/components/wifi/__init__.py +6 -6
- esphome/components/wifi/wifi_component.cpp +1 -1
- esphome/components/wifi/wifi_component_esp32_arduino.cpp +30 -1
- esphome/components/wireguard/__init__.py +2 -2
- esphome/components/xl9535/xl9535.h +2 -0
- esphome/components/xxtea/__init__.py +3 -0
- esphome/components/xxtea/xxtea.cpp +46 -0
- esphome/components/xxtea/xxtea.h +26 -0
- esphome/components/yashima/yashima.cpp +2 -1
- esphome/config.py +9 -5
- esphome/config_validation.py +55 -17
- esphome/const.py +7 -10
- esphome/core/__init__.py +6 -13
- esphome/core/base_automation.h +1 -0
- esphome/core/config.py +59 -72
- esphome/core/defines.h +9 -1
- esphome/core/gpio.h +7 -0
- esphome/core/helpers.cpp +19 -15
- esphome/core/helpers.h +57 -8
- esphome/core/log.h +9 -7
- esphome/cpp_generator.py +2 -2
- esphome/dashboard/web_server.py +1 -1
- esphome/espota2.py +3 -2
- esphome/loader.py +12 -4
- esphome/log.py +5 -7
- esphome/yaml_util.py +2 -2
- {esphome-2024.12.4.dist-info → esphome-2025.2.0.dist-info}/METADATA +14 -9
- {esphome-2024.12.4.dist-info → esphome-2025.2.0.dist-info}/RECORD +349 -300
- esphome/components/custom/binary_sensor/custom_binary_sensor.cpp +0 -16
- esphome/components/custom/binary_sensor/custom_binary_sensor.h +0 -26
- esphome/components/custom/climate/custom_climate.h +0 -22
- esphome/components/custom/cover/custom_cover.h +0 -21
- esphome/components/custom/light/custom_light_output.h +0 -24
- esphome/components/custom/output/custom_output.h +0 -37
- esphome/components/custom/sensor/custom_sensor.cpp +0 -16
- esphome/components/custom/sensor/custom_sensor.h +0 -24
- esphome/components/custom/switch/custom_switch.cpp +0 -16
- esphome/components/custom/switch/custom_switch.h +0 -24
- esphome/components/custom/text_sensor/custom_text_sensor.cpp +0 -16
- esphome/components/custom/text_sensor/custom_text_sensor.h +0 -26
- esphome/components/custom_component/custom_component.h +0 -28
- esphome/components/esp32_ble_server/ble_2901.cpp +0 -18
- esphome/components/esp32_ble_server/ble_2901.h +0 -19
- esphome/components/resistance_sampler/__init__.py +0 -6
- esphome/components/resistance_sampler/resistance_sampler.h +0 -10
- esphome/components/uptime/{sensor.py → sensor/__init__.py} +3 -3
- /esphome/components/uptime/{uptime_seconds_sensor.cpp → sensor/uptime_seconds_sensor.cpp} +0 -0
- /esphome/components/uptime/{uptime_seconds_sensor.h → sensor/uptime_seconds_sensor.h} +0 -0
- /esphome/components/uptime/{uptime_timestamp_sensor.cpp → sensor/uptime_timestamp_sensor.cpp} +0 -0
- /esphome/components/uptime/{uptime_timestamp_sensor.h → sensor/uptime_timestamp_sensor.h} +0 -0
- {esphome-2024.12.4.dist-info → esphome-2025.2.0.dist-info}/LICENSE +0 -0
- {esphome-2024.12.4.dist-info → esphome-2025.2.0.dist-info}/WHEEL +0 -0
- {esphome-2024.12.4.dist-info → esphome-2025.2.0.dist-info}/entry_points.txt +0 -0
- {esphome-2024.12.4.dist-info → esphome-2025.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,624 @@
|
|
1
|
+
#include "mixer_speaker.h"
|
2
|
+
|
3
|
+
#ifdef USE_ESP32
|
4
|
+
|
5
|
+
#include "esphome/core/hal.h"
|
6
|
+
#include "esphome/core/helpers.h"
|
7
|
+
#include "esphome/core/log.h"
|
8
|
+
|
9
|
+
#include <algorithm>
|
10
|
+
#include <cstring>
|
11
|
+
|
12
|
+
namespace esphome {
|
13
|
+
namespace mixer_speaker {
|
14
|
+
|
15
|
+
static const UBaseType_t MIXER_TASK_PRIORITY = 10;
|
16
|
+
|
17
|
+
static const uint32_t TRANSFER_BUFFER_DURATION_MS = 50;
|
18
|
+
static const uint32_t TASK_DELAY_MS = 25;
|
19
|
+
|
20
|
+
static const size_t TASK_STACK_SIZE = 4096;
|
21
|
+
|
22
|
+
static const int16_t MAX_AUDIO_SAMPLE_VALUE = INT16_MAX;
|
23
|
+
static const int16_t MIN_AUDIO_SAMPLE_VALUE = INT16_MIN;
|
24
|
+
|
25
|
+
static 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)
|
30
|
+
static 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
|
+
|
35
|
+
enum 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
|
+
|
45
|
+
void SourceSpeaker::dump_config() {
|
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
|
+
|
55
|
+
void SourceSpeaker::setup() {
|
56
|
+
this->parent_->get_output_speaker()->add_audio_output_callback(
|
57
|
+
[this](uint32_t new_playback_ms, uint32_t remainder_us, uint32_t pending_ms, uint32_t write_timestamp) {
|
58
|
+
uint32_t personal_playback_ms = std::min(new_playback_ms, this->pending_playback_ms_);
|
59
|
+
if (personal_playback_ms > 0) {
|
60
|
+
this->pending_playback_ms_ -= personal_playback_ms;
|
61
|
+
this->audio_output_callback_(personal_playback_ms, remainder_us, this->pending_playback_ms_, write_timestamp);
|
62
|
+
}
|
63
|
+
});
|
64
|
+
}
|
65
|
+
|
66
|
+
void SourceSpeaker::loop() {
|
67
|
+
switch (this->state_) {
|
68
|
+
case speaker::STATE_STARTING: {
|
69
|
+
esp_err_t err = this->start_();
|
70
|
+
if (err == ESP_OK) {
|
71
|
+
this->state_ = speaker::STATE_RUNNING;
|
72
|
+
this->stop_gracefully_ = false;
|
73
|
+
this->last_seen_data_ms_ = millis();
|
74
|
+
this->status_clear_error();
|
75
|
+
} else {
|
76
|
+
switch (err) {
|
77
|
+
case ESP_ERR_NO_MEM:
|
78
|
+
this->status_set_error("Failed to start mixer: not enough memory");
|
79
|
+
break;
|
80
|
+
case ESP_ERR_NOT_SUPPORTED:
|
81
|
+
this->status_set_error("Failed to start mixer: unsupported bits per sample");
|
82
|
+
break;
|
83
|
+
case ESP_ERR_INVALID_ARG:
|
84
|
+
this->status_set_error("Failed to start mixer: audio stream isn't compatible with the other audio stream.");
|
85
|
+
break;
|
86
|
+
case ESP_ERR_INVALID_STATE:
|
87
|
+
this->status_set_error("Failed to start mixer: mixer task failed to start");
|
88
|
+
break;
|
89
|
+
default:
|
90
|
+
this->status_set_error("Failed to start mixer");
|
91
|
+
break;
|
92
|
+
}
|
93
|
+
|
94
|
+
this->state_ = speaker::STATE_STOPPING;
|
95
|
+
}
|
96
|
+
break;
|
97
|
+
}
|
98
|
+
case speaker::STATE_RUNNING:
|
99
|
+
if (!this->transfer_buffer_->has_buffered_data()) {
|
100
|
+
if ((this->timeout_ms_.has_value() && ((millis() - this->last_seen_data_ms_) > this->timeout_ms_.value())) ||
|
101
|
+
this->stop_gracefully_) {
|
102
|
+
this->state_ = speaker::STATE_STOPPING;
|
103
|
+
}
|
104
|
+
}
|
105
|
+
break;
|
106
|
+
case speaker::STATE_STOPPING:
|
107
|
+
this->stop_();
|
108
|
+
this->stop_gracefully_ = false;
|
109
|
+
this->state_ = speaker::STATE_STOPPED;
|
110
|
+
break;
|
111
|
+
case speaker::STATE_STOPPED:
|
112
|
+
break;
|
113
|
+
}
|
114
|
+
}
|
115
|
+
|
116
|
+
size_t SourceSpeaker::play(const uint8_t *data, size_t length, TickType_t ticks_to_wait) {
|
117
|
+
if (this->is_stopped()) {
|
118
|
+
this->start();
|
119
|
+
}
|
120
|
+
size_t bytes_written = 0;
|
121
|
+
if (this->ring_buffer_.use_count() == 1) {
|
122
|
+
std::shared_ptr<RingBuffer> temp_ring_buffer = this->ring_buffer_.lock();
|
123
|
+
bytes_written = temp_ring_buffer->write_without_replacement(data, length, ticks_to_wait);
|
124
|
+
if (bytes_written > 0) {
|
125
|
+
this->last_seen_data_ms_ = millis();
|
126
|
+
}
|
127
|
+
}
|
128
|
+
return bytes_written;
|
129
|
+
}
|
130
|
+
|
131
|
+
void SourceSpeaker::start() { this->state_ = speaker::STATE_STARTING; }
|
132
|
+
|
133
|
+
esp_err_t SourceSpeaker::start_() {
|
134
|
+
const size_t ring_buffer_size = this->audio_stream_info_.ms_to_bytes(this->buffer_duration_ms_);
|
135
|
+
if (this->transfer_buffer_.use_count() == 0) {
|
136
|
+
this->transfer_buffer_ =
|
137
|
+
audio::AudioSourceTransferBuffer::create(this->audio_stream_info_.ms_to_bytes(TRANSFER_BUFFER_DURATION_MS));
|
138
|
+
|
139
|
+
if (this->transfer_buffer_ == nullptr) {
|
140
|
+
return ESP_ERR_NO_MEM;
|
141
|
+
}
|
142
|
+
std::shared_ptr<RingBuffer> temp_ring_buffer;
|
143
|
+
|
144
|
+
if (!this->ring_buffer_.use_count()) {
|
145
|
+
temp_ring_buffer = RingBuffer::create(ring_buffer_size);
|
146
|
+
this->ring_buffer_ = temp_ring_buffer;
|
147
|
+
}
|
148
|
+
|
149
|
+
if (!this->ring_buffer_.use_count()) {
|
150
|
+
return ESP_ERR_NO_MEM;
|
151
|
+
} else {
|
152
|
+
this->transfer_buffer_->set_source(temp_ring_buffer);
|
153
|
+
}
|
154
|
+
}
|
155
|
+
|
156
|
+
return this->parent_->start(this->audio_stream_info_);
|
157
|
+
}
|
158
|
+
|
159
|
+
void SourceSpeaker::stop() {
|
160
|
+
if (this->state_ != speaker::STATE_STOPPED) {
|
161
|
+
this->state_ = speaker::STATE_STOPPING;
|
162
|
+
}
|
163
|
+
}
|
164
|
+
|
165
|
+
void SourceSpeaker::stop_() {
|
166
|
+
this->transfer_buffer_.reset(); // deallocates the transfer buffer
|
167
|
+
}
|
168
|
+
|
169
|
+
void SourceSpeaker::finish() { this->stop_gracefully_ = true; }
|
170
|
+
|
171
|
+
bool SourceSpeaker::has_buffered_data() const {
|
172
|
+
return ((this->transfer_buffer_.use_count() > 0) && this->transfer_buffer_->has_buffered_data());
|
173
|
+
}
|
174
|
+
|
175
|
+
void SourceSpeaker::set_mute_state(bool mute_state) {
|
176
|
+
this->mute_state_ = mute_state;
|
177
|
+
this->parent_->get_output_speaker()->set_mute_state(mute_state);
|
178
|
+
}
|
179
|
+
|
180
|
+
void SourceSpeaker::set_volume(float volume) {
|
181
|
+
this->volume_ = volume;
|
182
|
+
this->parent_->get_output_speaker()->set_volume(volume);
|
183
|
+
}
|
184
|
+
|
185
|
+
size_t SourceSpeaker::process_data_from_source(TickType_t ticks_to_wait) {
|
186
|
+
if (!this->transfer_buffer_.use_count()) {
|
187
|
+
return 0;
|
188
|
+
}
|
189
|
+
|
190
|
+
// Store current offset, as these samples are already ducked
|
191
|
+
const size_t current_length = this->transfer_buffer_->available();
|
192
|
+
|
193
|
+
size_t bytes_read = this->transfer_buffer_->transfer_data_from_source(ticks_to_wait);
|
194
|
+
|
195
|
+
uint32_t samples_to_duck = this->audio_stream_info_.bytes_to_samples(bytes_read);
|
196
|
+
if (samples_to_duck > 0) {
|
197
|
+
int16_t *current_buffer = reinterpret_cast<int16_t *>(this->transfer_buffer_->get_buffer_start() + current_length);
|
198
|
+
|
199
|
+
duck_samples(current_buffer, samples_to_duck, &this->current_ducking_db_reduction_,
|
200
|
+
&this->ducking_transition_samples_remaining_, this->samples_per_ducking_step_,
|
201
|
+
this->db_change_per_ducking_step_);
|
202
|
+
}
|
203
|
+
|
204
|
+
return bytes_read;
|
205
|
+
}
|
206
|
+
|
207
|
+
void SourceSpeaker::apply_ducking(uint8_t decibel_reduction, uint32_t duration) {
|
208
|
+
if (this->target_ducking_db_reduction_ != decibel_reduction) {
|
209
|
+
this->current_ducking_db_reduction_ = this->target_ducking_db_reduction_;
|
210
|
+
|
211
|
+
this->target_ducking_db_reduction_ = decibel_reduction;
|
212
|
+
|
213
|
+
uint8_t total_ducking_steps = 0;
|
214
|
+
if (this->target_ducking_db_reduction_ > this->current_ducking_db_reduction_) {
|
215
|
+
// The dB reduction level is increasing (which results in quieter audio)
|
216
|
+
total_ducking_steps = this->target_ducking_db_reduction_ - this->current_ducking_db_reduction_ - 1;
|
217
|
+
this->db_change_per_ducking_step_ = 1;
|
218
|
+
} else {
|
219
|
+
// The dB reduction level is decreasing (which results in louder audio)
|
220
|
+
total_ducking_steps = this->current_ducking_db_reduction_ - this->target_ducking_db_reduction_ - 1;
|
221
|
+
this->db_change_per_ducking_step_ = -1;
|
222
|
+
}
|
223
|
+
if ((duration > 0) && (total_ducking_steps > 0)) {
|
224
|
+
this->ducking_transition_samples_remaining_ = this->audio_stream_info_.ms_to_samples(duration);
|
225
|
+
|
226
|
+
this->samples_per_ducking_step_ = this->ducking_transition_samples_remaining_ / total_ducking_steps;
|
227
|
+
this->ducking_transition_samples_remaining_ =
|
228
|
+
this->samples_per_ducking_step_ * total_ducking_steps; // Adjust for integer division rounding
|
229
|
+
|
230
|
+
this->current_ducking_db_reduction_ += this->db_change_per_ducking_step_;
|
231
|
+
} else {
|
232
|
+
this->ducking_transition_samples_remaining_ = 0;
|
233
|
+
this->current_ducking_db_reduction_ = this->target_ducking_db_reduction_;
|
234
|
+
}
|
235
|
+
}
|
236
|
+
}
|
237
|
+
|
238
|
+
void SourceSpeaker::duck_samples(int16_t *input_buffer, uint32_t input_samples_to_duck,
|
239
|
+
int8_t *current_ducking_db_reduction, uint32_t *ducking_transition_samples_remaining,
|
240
|
+
uint32_t samples_per_ducking_step, int8_t db_change_per_ducking_step) {
|
241
|
+
if (*ducking_transition_samples_remaining > 0) {
|
242
|
+
// Ducking level is still transitioning
|
243
|
+
|
244
|
+
// Takes the ceiling of input_samples_to_duck/samples_per_ducking_step
|
245
|
+
uint32_t ducking_steps_in_batch =
|
246
|
+
input_samples_to_duck / samples_per_ducking_step + (input_samples_to_duck % samples_per_ducking_step != 0);
|
247
|
+
|
248
|
+
for (uint32_t i = 0; i < ducking_steps_in_batch; ++i) {
|
249
|
+
uint32_t samples_left_in_step = *ducking_transition_samples_remaining % samples_per_ducking_step;
|
250
|
+
|
251
|
+
if (samples_left_in_step == 0) {
|
252
|
+
samples_left_in_step = samples_per_ducking_step;
|
253
|
+
}
|
254
|
+
|
255
|
+
uint32_t samples_to_duck = std::min(input_samples_to_duck, samples_left_in_step);
|
256
|
+
samples_to_duck = std::min(samples_to_duck, *ducking_transition_samples_remaining);
|
257
|
+
|
258
|
+
// Ensure we only point to valid index in the Q15 scaling factor table
|
259
|
+
uint8_t safe_db_reduction_index =
|
260
|
+
clamp<uint8_t>(*current_ducking_db_reduction, 0, DECIBEL_REDUCTION_TABLE.size() - 1);
|
261
|
+
int16_t q15_scale_factor = DECIBEL_REDUCTION_TABLE[safe_db_reduction_index];
|
262
|
+
|
263
|
+
audio::scale_audio_samples(input_buffer, input_buffer, q15_scale_factor, samples_to_duck);
|
264
|
+
|
265
|
+
if (samples_left_in_step - samples_to_duck == 0) {
|
266
|
+
// After scaling the current samples, we are ready to transition to the next step
|
267
|
+
*current_ducking_db_reduction += db_change_per_ducking_step;
|
268
|
+
}
|
269
|
+
|
270
|
+
input_buffer += samples_to_duck;
|
271
|
+
*ducking_transition_samples_remaining -= samples_to_duck;
|
272
|
+
input_samples_to_duck -= samples_to_duck;
|
273
|
+
}
|
274
|
+
}
|
275
|
+
|
276
|
+
if ((*current_ducking_db_reduction > 0) && (input_samples_to_duck > 0)) {
|
277
|
+
// Audio is ducked, but its not in the middle of a transition step
|
278
|
+
|
279
|
+
uint8_t safe_db_reduction_index =
|
280
|
+
clamp<uint8_t>(*current_ducking_db_reduction, 0, DECIBEL_REDUCTION_TABLE.size() - 1);
|
281
|
+
int16_t q15_scale_factor = DECIBEL_REDUCTION_TABLE[safe_db_reduction_index];
|
282
|
+
|
283
|
+
audio::scale_audio_samples(input_buffer, input_buffer, q15_scale_factor, input_samples_to_duck);
|
284
|
+
}
|
285
|
+
}
|
286
|
+
|
287
|
+
void MixerSpeaker::dump_config() {
|
288
|
+
ESP_LOGCONFIG(TAG, "Speaker Mixer:");
|
289
|
+
ESP_LOGCONFIG(TAG, " Number of output channels: %u", this->output_channels_);
|
290
|
+
}
|
291
|
+
|
292
|
+
void MixerSpeaker::setup() {
|
293
|
+
this->event_group_ = xEventGroupCreate();
|
294
|
+
|
295
|
+
if (this->event_group_ == nullptr) {
|
296
|
+
ESP_LOGE(TAG, "Failed to create event group");
|
297
|
+
this->mark_failed();
|
298
|
+
return;
|
299
|
+
}
|
300
|
+
}
|
301
|
+
|
302
|
+
void MixerSpeaker::loop() {
|
303
|
+
uint32_t event_group_bits = xEventGroupGetBits(this->event_group_);
|
304
|
+
|
305
|
+
if (event_group_bits & MixerEventGroupBits::STATE_STARTING) {
|
306
|
+
ESP_LOGD(TAG, "Starting speaker mixer");
|
307
|
+
xEventGroupClearBits(this->event_group_, MixerEventGroupBits::STATE_STARTING);
|
308
|
+
}
|
309
|
+
if (event_group_bits & MixerEventGroupBits::ERR_ESP_NO_MEM) {
|
310
|
+
this->status_set_error("Failed to allocate the mixer's internal buffer");
|
311
|
+
xEventGroupClearBits(this->event_group_, MixerEventGroupBits::ERR_ESP_NO_MEM);
|
312
|
+
}
|
313
|
+
if (event_group_bits & MixerEventGroupBits::STATE_RUNNING) {
|
314
|
+
ESP_LOGD(TAG, "Started speaker mixer");
|
315
|
+
this->status_clear_error();
|
316
|
+
xEventGroupClearBits(this->event_group_, MixerEventGroupBits::STATE_RUNNING);
|
317
|
+
}
|
318
|
+
if (event_group_bits & MixerEventGroupBits::STATE_STOPPING) {
|
319
|
+
ESP_LOGD(TAG, "Stopping speaker mixer");
|
320
|
+
xEventGroupClearBits(this->event_group_, MixerEventGroupBits::STATE_STOPPING);
|
321
|
+
}
|
322
|
+
if (event_group_bits & MixerEventGroupBits::STATE_STOPPED) {
|
323
|
+
if (this->delete_task_() == ESP_OK) {
|
324
|
+
xEventGroupClearBits(this->event_group_, MixerEventGroupBits::ALL_BITS);
|
325
|
+
}
|
326
|
+
}
|
327
|
+
|
328
|
+
if (this->task_handle_ != nullptr) {
|
329
|
+
bool all_stopped = true;
|
330
|
+
|
331
|
+
for (auto &speaker : this->source_speakers_) {
|
332
|
+
all_stopped &= speaker->is_stopped();
|
333
|
+
}
|
334
|
+
|
335
|
+
if (all_stopped) {
|
336
|
+
this->stop();
|
337
|
+
}
|
338
|
+
}
|
339
|
+
}
|
340
|
+
|
341
|
+
esp_err_t MixerSpeaker::start(audio::AudioStreamInfo &stream_info) {
|
342
|
+
if (!this->audio_stream_info_.has_value()) {
|
343
|
+
if (stream_info.get_bits_per_sample() != 16) {
|
344
|
+
// Audio streams that don't have 16 bits per sample are not supported
|
345
|
+
return ESP_ERR_NOT_SUPPORTED;
|
346
|
+
}
|
347
|
+
|
348
|
+
this->audio_stream_info_ = audio::AudioStreamInfo(stream_info.get_bits_per_sample(), this->output_channels_,
|
349
|
+
stream_info.get_sample_rate());
|
350
|
+
this->output_speaker_->set_audio_stream_info(this->audio_stream_info_.value());
|
351
|
+
} else {
|
352
|
+
if (!this->queue_mode_ && (stream_info.get_sample_rate() != this->audio_stream_info_.value().get_sample_rate())) {
|
353
|
+
// The two audio streams must have the same sample rate to mix properly if not in queue mode
|
354
|
+
return ESP_ERR_INVALID_ARG;
|
355
|
+
}
|
356
|
+
}
|
357
|
+
|
358
|
+
return this->start_task_();
|
359
|
+
}
|
360
|
+
|
361
|
+
esp_err_t MixerSpeaker::start_task_() {
|
362
|
+
if (this->task_stack_buffer_ == nullptr) {
|
363
|
+
if (this->task_stack_in_psram_) {
|
364
|
+
RAMAllocator<StackType_t> stack_allocator(RAMAllocator<StackType_t>::ALLOC_EXTERNAL);
|
365
|
+
this->task_stack_buffer_ = stack_allocator.allocate(TASK_STACK_SIZE);
|
366
|
+
} else {
|
367
|
+
RAMAllocator<StackType_t> stack_allocator(RAMAllocator<StackType_t>::ALLOC_INTERNAL);
|
368
|
+
this->task_stack_buffer_ = stack_allocator.allocate(TASK_STACK_SIZE);
|
369
|
+
}
|
370
|
+
}
|
371
|
+
|
372
|
+
if (this->task_stack_buffer_ == nullptr) {
|
373
|
+
return ESP_ERR_NO_MEM;
|
374
|
+
}
|
375
|
+
|
376
|
+
if (this->task_handle_ == nullptr) {
|
377
|
+
this->task_handle_ = xTaskCreateStatic(audio_mixer_task, "mixer", TASK_STACK_SIZE, (void *) this,
|
378
|
+
MIXER_TASK_PRIORITY, this->task_stack_buffer_, &this->task_stack_);
|
379
|
+
}
|
380
|
+
|
381
|
+
if (this->task_handle_ == nullptr) {
|
382
|
+
return ESP_ERR_INVALID_STATE;
|
383
|
+
}
|
384
|
+
|
385
|
+
return ESP_OK;
|
386
|
+
}
|
387
|
+
|
388
|
+
esp_err_t MixerSpeaker::delete_task_() {
|
389
|
+
if (!this->task_created_) {
|
390
|
+
this->task_handle_ = nullptr;
|
391
|
+
|
392
|
+
if (this->task_stack_buffer_ != nullptr) {
|
393
|
+
if (this->task_stack_in_psram_) {
|
394
|
+
RAMAllocator<StackType_t> stack_allocator(RAMAllocator<StackType_t>::ALLOC_EXTERNAL);
|
395
|
+
stack_allocator.deallocate(this->task_stack_buffer_, TASK_STACK_SIZE);
|
396
|
+
} else {
|
397
|
+
RAMAllocator<StackType_t> stack_allocator(RAMAllocator<StackType_t>::ALLOC_INTERNAL);
|
398
|
+
stack_allocator.deallocate(this->task_stack_buffer_, TASK_STACK_SIZE);
|
399
|
+
}
|
400
|
+
|
401
|
+
this->task_stack_buffer_ = nullptr;
|
402
|
+
}
|
403
|
+
|
404
|
+
return ESP_OK;
|
405
|
+
}
|
406
|
+
|
407
|
+
return ESP_ERR_INVALID_STATE;
|
408
|
+
}
|
409
|
+
|
410
|
+
void MixerSpeaker::stop() { xEventGroupSetBits(this->event_group_, MixerEventGroupBits::COMMAND_STOP); }
|
411
|
+
|
412
|
+
void MixerSpeaker::copy_frames(const int16_t *input_buffer, audio::AudioStreamInfo input_stream_info,
|
413
|
+
int16_t *output_buffer, audio::AudioStreamInfo output_stream_info,
|
414
|
+
uint32_t frames_to_transfer) {
|
415
|
+
uint8_t input_channels = input_stream_info.get_channels();
|
416
|
+
uint8_t output_channels = output_stream_info.get_channels();
|
417
|
+
const uint8_t max_input_channel_index = input_channels - 1;
|
418
|
+
|
419
|
+
if (input_channels == output_channels) {
|
420
|
+
size_t bytes_to_copy = input_stream_info.frames_to_bytes(frames_to_transfer);
|
421
|
+
memcpy(output_buffer, input_buffer, bytes_to_copy);
|
422
|
+
|
423
|
+
return;
|
424
|
+
}
|
425
|
+
|
426
|
+
for (uint32_t frame_index = 0; frame_index < frames_to_transfer; ++frame_index) {
|
427
|
+
for (uint8_t output_channel_index = 0; output_channel_index < output_channels; ++output_channel_index) {
|
428
|
+
uint8_t input_channel_index = std::min(output_channel_index, max_input_channel_index);
|
429
|
+
output_buffer[output_channels * frame_index + output_channel_index] =
|
430
|
+
input_buffer[input_channels * frame_index + input_channel_index];
|
431
|
+
}
|
432
|
+
}
|
433
|
+
}
|
434
|
+
|
435
|
+
void MixerSpeaker::mix_audio_samples(const int16_t *primary_buffer, audio::AudioStreamInfo primary_stream_info,
|
436
|
+
const int16_t *secondary_buffer, audio::AudioStreamInfo secondary_stream_info,
|
437
|
+
int16_t *output_buffer, audio::AudioStreamInfo output_stream_info,
|
438
|
+
uint32_t frames_to_mix) {
|
439
|
+
const uint8_t primary_channels = primary_stream_info.get_channels();
|
440
|
+
const uint8_t secondary_channels = secondary_stream_info.get_channels();
|
441
|
+
const uint8_t output_channels = output_stream_info.get_channels();
|
442
|
+
|
443
|
+
const uint8_t max_primary_channel_index = primary_channels - 1;
|
444
|
+
const uint8_t max_secondary_channel_index = secondary_channels - 1;
|
445
|
+
|
446
|
+
for (uint32_t frames_index = 0; frames_index < frames_to_mix; ++frames_index) {
|
447
|
+
for (uint8_t output_channel_index = 0; output_channel_index < output_channels; ++output_channel_index) {
|
448
|
+
const uint32_t secondary_channel_index = std::min(output_channel_index, max_secondary_channel_index);
|
449
|
+
const int32_t secondary_sample = secondary_buffer[frames_index * secondary_channels + secondary_channel_index];
|
450
|
+
|
451
|
+
const uint32_t primary_channel_index = std::min(output_channel_index, max_primary_channel_index);
|
452
|
+
const int32_t primary_sample =
|
453
|
+
static_cast<int32_t>(primary_buffer[frames_index * primary_channels + primary_channel_index]);
|
454
|
+
|
455
|
+
const int32_t added_sample = secondary_sample + primary_sample;
|
456
|
+
|
457
|
+
output_buffer[frames_index * output_channels + output_channel_index] =
|
458
|
+
static_cast<int16_t>(clamp<int32_t>(added_sample, MIN_AUDIO_SAMPLE_VALUE, MAX_AUDIO_SAMPLE_VALUE));
|
459
|
+
}
|
460
|
+
}
|
461
|
+
}
|
462
|
+
|
463
|
+
void MixerSpeaker::audio_mixer_task(void *params) {
|
464
|
+
MixerSpeaker *this_mixer = (MixerSpeaker *) params;
|
465
|
+
|
466
|
+
xEventGroupSetBits(this_mixer->event_group_, MixerEventGroupBits::STATE_STARTING);
|
467
|
+
|
468
|
+
this_mixer->task_created_ = true;
|
469
|
+
|
470
|
+
std::unique_ptr<audio::AudioSinkTransferBuffer> output_transfer_buffer = audio::AudioSinkTransferBuffer::create(
|
471
|
+
this_mixer->audio_stream_info_.value().ms_to_bytes(TRANSFER_BUFFER_DURATION_MS));
|
472
|
+
|
473
|
+
if (output_transfer_buffer == nullptr) {
|
474
|
+
xEventGroupSetBits(this_mixer->event_group_,
|
475
|
+
MixerEventGroupBits::STATE_STOPPED | MixerEventGroupBits::ERR_ESP_NO_MEM);
|
476
|
+
|
477
|
+
this_mixer->task_created_ = false;
|
478
|
+
vTaskDelete(nullptr);
|
479
|
+
}
|
480
|
+
|
481
|
+
output_transfer_buffer->set_sink(this_mixer->output_speaker_);
|
482
|
+
|
483
|
+
xEventGroupSetBits(this_mixer->event_group_, MixerEventGroupBits::STATE_RUNNING);
|
484
|
+
|
485
|
+
bool sent_finished = false;
|
486
|
+
|
487
|
+
while (true) {
|
488
|
+
uint32_t event_group_bits = xEventGroupGetBits(this_mixer->event_group_);
|
489
|
+
if (event_group_bits & MixerEventGroupBits::COMMAND_STOP) {
|
490
|
+
break;
|
491
|
+
}
|
492
|
+
|
493
|
+
output_transfer_buffer->transfer_data_to_sink(pdMS_TO_TICKS(TASK_DELAY_MS));
|
494
|
+
|
495
|
+
const uint32_t output_frames_free =
|
496
|
+
this_mixer->audio_stream_info_.value().bytes_to_frames(output_transfer_buffer->free());
|
497
|
+
|
498
|
+
std::vector<SourceSpeaker *> speakers_with_data;
|
499
|
+
std::vector<std::shared_ptr<audio::AudioSourceTransferBuffer>> transfer_buffers_with_data;
|
500
|
+
|
501
|
+
for (auto &speaker : this_mixer->source_speakers_) {
|
502
|
+
if (speaker->get_transfer_buffer().use_count() > 0) {
|
503
|
+
std::shared_ptr<audio::AudioSourceTransferBuffer> transfer_buffer = speaker->get_transfer_buffer().lock();
|
504
|
+
speaker->process_data_from_source(0); // Transfers and ducks audio from source ring buffers
|
505
|
+
|
506
|
+
if ((transfer_buffer->available() > 0) && !speaker->get_pause_state()) {
|
507
|
+
// Store the locked transfer buffers in their own vector to avoid releasing ownership until after the loop
|
508
|
+
transfer_buffers_with_data.push_back(transfer_buffer);
|
509
|
+
speakers_with_data.push_back(speaker);
|
510
|
+
}
|
511
|
+
}
|
512
|
+
}
|
513
|
+
|
514
|
+
if (transfer_buffers_with_data.empty()) {
|
515
|
+
// No audio available for transferring, block task temporarily
|
516
|
+
delay(TASK_DELAY_MS);
|
517
|
+
continue;
|
518
|
+
}
|
519
|
+
|
520
|
+
uint32_t frames_to_mix = output_frames_free;
|
521
|
+
|
522
|
+
if ((transfer_buffers_with_data.size() == 1) || this_mixer->queue_mode_) {
|
523
|
+
// Only one speaker has audio data, just copy samples over
|
524
|
+
|
525
|
+
audio::AudioStreamInfo active_stream_info = speakers_with_data[0]->get_audio_stream_info();
|
526
|
+
|
527
|
+
if (active_stream_info.get_sample_rate() ==
|
528
|
+
this_mixer->output_speaker_->get_audio_stream_info().get_sample_rate()) {
|
529
|
+
// Speaker's sample rate matches the output speaker's, copy directly
|
530
|
+
|
531
|
+
const uint32_t frames_available_in_buffer =
|
532
|
+
active_stream_info.bytes_to_frames(transfer_buffers_with_data[0]->available());
|
533
|
+
frames_to_mix = std::min(frames_to_mix, frames_available_in_buffer);
|
534
|
+
copy_frames(reinterpret_cast<int16_t *>(transfer_buffers_with_data[0]->get_buffer_start()), active_stream_info,
|
535
|
+
reinterpret_cast<int16_t *>(output_transfer_buffer->get_buffer_end()),
|
536
|
+
this_mixer->audio_stream_info_.value(), frames_to_mix);
|
537
|
+
|
538
|
+
// Update source speaker buffer length
|
539
|
+
transfer_buffers_with_data[0]->decrease_buffer_length(active_stream_info.frames_to_bytes(frames_to_mix));
|
540
|
+
speakers_with_data[0]->accumulated_frames_read_ += frames_to_mix;
|
541
|
+
|
542
|
+
// Add new audio duration to the source speaker pending playback
|
543
|
+
speakers_with_data[0]->pending_playback_ms_ +=
|
544
|
+
active_stream_info.frames_to_milliseconds_with_remainder(&speakers_with_data[0]->accumulated_frames_read_);
|
545
|
+
|
546
|
+
// Update output transfer buffer length
|
547
|
+
output_transfer_buffer->increase_buffer_length(
|
548
|
+
this_mixer->audio_stream_info_.value().frames_to_bytes(frames_to_mix));
|
549
|
+
} else {
|
550
|
+
// Speaker's stream info doesn't match the output speaker's, so it's a new source speaker
|
551
|
+
if (!this_mixer->output_speaker_->is_stopped()) {
|
552
|
+
if (!sent_finished) {
|
553
|
+
this_mixer->output_speaker_->finish();
|
554
|
+
sent_finished = true; // Avoid repeatedly sending the finish command
|
555
|
+
}
|
556
|
+
} else {
|
557
|
+
// Speaker has finished writing the current audio, update the stream information and restart the speaker
|
558
|
+
this_mixer->audio_stream_info_ =
|
559
|
+
audio::AudioStreamInfo(active_stream_info.get_bits_per_sample(), this_mixer->output_channels_,
|
560
|
+
active_stream_info.get_sample_rate());
|
561
|
+
this_mixer->output_speaker_->set_audio_stream_info(this_mixer->audio_stream_info_.value());
|
562
|
+
this_mixer->output_speaker_->start();
|
563
|
+
sent_finished = false;
|
564
|
+
}
|
565
|
+
}
|
566
|
+
} else {
|
567
|
+
// Determine how many frames to mix
|
568
|
+
for (int i = 0; i < transfer_buffers_with_data.size(); ++i) {
|
569
|
+
const uint32_t frames_available_in_buffer =
|
570
|
+
speakers_with_data[i]->get_audio_stream_info().bytes_to_frames(transfer_buffers_with_data[i]->available());
|
571
|
+
frames_to_mix = std::min(frames_to_mix, frames_available_in_buffer);
|
572
|
+
}
|
573
|
+
int16_t *primary_buffer = reinterpret_cast<int16_t *>(transfer_buffers_with_data[0]->get_buffer_start());
|
574
|
+
audio::AudioStreamInfo primary_stream_info = speakers_with_data[0]->get_audio_stream_info();
|
575
|
+
|
576
|
+
// Mix two streams together
|
577
|
+
for (int i = 1; i < transfer_buffers_with_data.size(); ++i) {
|
578
|
+
mix_audio_samples(primary_buffer, primary_stream_info,
|
579
|
+
reinterpret_cast<int16_t *>(transfer_buffers_with_data[i]->get_buffer_start()),
|
580
|
+
speakers_with_data[i]->get_audio_stream_info(),
|
581
|
+
reinterpret_cast<int16_t *>(output_transfer_buffer->get_buffer_end()),
|
582
|
+
this_mixer->audio_stream_info_.value(), frames_to_mix);
|
583
|
+
|
584
|
+
speakers_with_data[i]->pending_playback_ms_ +=
|
585
|
+
speakers_with_data[i]->get_audio_stream_info().frames_to_milliseconds_with_remainder(
|
586
|
+
&speakers_with_data[i]->accumulated_frames_read_);
|
587
|
+
|
588
|
+
if (i != transfer_buffers_with_data.size() - 1) {
|
589
|
+
// Need to mix more streams together, point primary buffer and stream info to the already mixed output
|
590
|
+
primary_buffer = reinterpret_cast<int16_t *>(output_transfer_buffer->get_buffer_end());
|
591
|
+
primary_stream_info = this_mixer->audio_stream_info_.value();
|
592
|
+
}
|
593
|
+
}
|
594
|
+
|
595
|
+
// Update source transfer buffer lengths and add new audio durations to the source speaker pending playbacks
|
596
|
+
for (int i = 0; i < transfer_buffers_with_data.size(); ++i) {
|
597
|
+
transfer_buffers_with_data[i]->decrease_buffer_length(
|
598
|
+
speakers_with_data[i]->get_audio_stream_info().frames_to_bytes(frames_to_mix));
|
599
|
+
speakers_with_data[i]->accumulated_frames_read_ += frames_to_mix;
|
600
|
+
|
601
|
+
speakers_with_data[i]->pending_playback_ms_ +=
|
602
|
+
speakers_with_data[i]->get_audio_stream_info().frames_to_milliseconds_with_remainder(
|
603
|
+
&speakers_with_data[i]->accumulated_frames_read_);
|
604
|
+
}
|
605
|
+
|
606
|
+
// Update output transfer buffer length
|
607
|
+
output_transfer_buffer->increase_buffer_length(
|
608
|
+
this_mixer->audio_stream_info_.value().frames_to_bytes(frames_to_mix));
|
609
|
+
}
|
610
|
+
}
|
611
|
+
|
612
|
+
xEventGroupSetBits(this_mixer->event_group_, MixerEventGroupBits::STATE_STOPPING);
|
613
|
+
|
614
|
+
output_transfer_buffer.reset();
|
615
|
+
|
616
|
+
xEventGroupSetBits(this_mixer->event_group_, MixerEventGroupBits::STATE_STOPPED);
|
617
|
+
this_mixer->task_created_ = false;
|
618
|
+
vTaskDelete(nullptr);
|
619
|
+
}
|
620
|
+
|
621
|
+
} // namespace mixer_speaker
|
622
|
+
} // namespace esphome
|
623
|
+
|
624
|
+
#endif
|