esphome 2024.12.3__py3-none-any.whl → 2025.2.0b1__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/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/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 +2 -1
- esphome/components/display/__init__.py +18 -5
- esphome/components/display/display.cpp +2 -1
- 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_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/gcja5/gcja5.cpp +2 -1
- 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 +43 -7
- esphome/components/logger/logger.cpp +16 -11
- 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/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 +28 -2
- esphome/components/online_image/image_decoder.h +24 -15
- esphome/components/online_image/jpeg_image.cpp +90 -0
- esphome/components/online_image/jpeg_image.h +34 -0
- esphome/components/online_image/online_image.cpp +112 -53
- esphome/components/online_image/online_image.h +24 -7
- 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/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 +57 -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/espota2.py +3 -2
- esphome/loader.py +12 -4
- esphome/log.py +5 -7
- esphome/yaml_util.py +2 -2
- {esphome-2024.12.3.dist-info → esphome-2025.2.0b1.dist-info}/METADATA +12 -7
- {esphome-2024.12.3.dist-info → esphome-2025.2.0b1.dist-info}/RECORD +338 -289
- 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.3.dist-info → esphome-2025.2.0b1.dist-info}/LICENSE +0 -0
- {esphome-2024.12.3.dist-info → esphome-2025.2.0b1.dist-info}/WHEEL +0 -0
- {esphome-2024.12.3.dist-info → esphome-2025.2.0b1.dist-info}/entry_points.txt +0 -0
- {esphome-2024.12.3.dist-info → esphome-2025.2.0b1.dist-info}/top_level.txt +0 -0
esphome/components/audio/audio.h
CHANGED
@@ -1,21 +1,139 @@
|
|
1
1
|
#pragma once
|
2
2
|
|
3
|
+
#include "esphome/core/defines.h"
|
4
|
+
|
3
5
|
#include <cstddef>
|
4
6
|
#include <cstdint>
|
5
7
|
|
6
8
|
namespace esphome {
|
7
9
|
namespace audio {
|
8
10
|
|
9
|
-
|
10
|
-
|
11
|
-
|
11
|
+
class AudioStreamInfo {
|
12
|
+
/* Class to respresent important parameters of the audio stream that also provides helper function to convert between
|
13
|
+
* various audio related units.
|
14
|
+
*
|
15
|
+
* - An audio sample represents a unit of audio for one channel.
|
16
|
+
* - A frame represents a unit of audio with a sample for every channel.
|
17
|
+
*
|
18
|
+
* In gneneral, converting between bytes, samples, and frames shouldn't result in rounding errors so long as frames
|
19
|
+
* are used as the main unit when transferring audio data. Durations may result in rounding for certain sample rates;
|
20
|
+
* e.g., 44.1 KHz. The ``frames_to_milliseconds_with_remainder`` function should be used for accuracy, as it takes
|
21
|
+
* into account the remainder rather than just ignoring any rounding.
|
22
|
+
*/
|
23
|
+
public:
|
24
|
+
AudioStreamInfo()
|
25
|
+
: AudioStreamInfo(16, 1, 16000){}; // Default values represent ESPHome's audio components historical values
|
26
|
+
AudioStreamInfo(uint8_t bits_per_sample, uint8_t channels, uint32_t sample_rate);
|
27
|
+
|
28
|
+
uint8_t get_bits_per_sample() const { return this->bits_per_sample_; }
|
29
|
+
uint8_t get_channels() const { return this->channels_; }
|
30
|
+
uint32_t get_sample_rate() const { return this->sample_rate_; }
|
31
|
+
|
32
|
+
/// @brief Convert bytes to duration in milliseconds.
|
33
|
+
/// @param bytes Number of bytes to convert
|
34
|
+
/// @return Duration in milliseconds that will store `bytes` bytes of audio. May round down for certain sample rates
|
35
|
+
/// or values of `bytes`.
|
36
|
+
uint32_t bytes_to_ms(size_t bytes) const {
|
37
|
+
return bytes * 1000 / (this->sample_rate_ * this->bytes_per_sample_ * this->channels_);
|
38
|
+
}
|
39
|
+
|
40
|
+
/// @brief Convert bytes to frames.
|
41
|
+
/// @param bytes Number of bytes to convert
|
42
|
+
/// @return Audio frames that will store `bytes` bytes.
|
43
|
+
uint32_t bytes_to_frames(size_t bytes) const { return (bytes / (this->bytes_per_sample_ * this->channels_)); }
|
44
|
+
|
45
|
+
/// @brief Convert bytes to samples.
|
46
|
+
/// @param bytes Number of bytes to convert
|
47
|
+
/// @return Audio samples that will store `bytes` bytes.
|
48
|
+
uint32_t bytes_to_samples(size_t bytes) const { return (bytes / this->bytes_per_sample_); }
|
49
|
+
|
50
|
+
/// @brief Converts frames to bytes.
|
51
|
+
/// @param frames Number of frames to convert.
|
52
|
+
/// @return Number of bytes that will store `frames` frames of audio.
|
53
|
+
size_t frames_to_bytes(uint32_t frames) const { return frames * this->bytes_per_sample_ * this->channels_; }
|
54
|
+
|
55
|
+
/// @brief Converts samples to bytes.
|
56
|
+
/// @param samples Number of samples to convert.
|
57
|
+
/// @return Number of bytes that will store `samples` samples of audio.
|
58
|
+
size_t samples_to_bytes(uint32_t samples) const { return samples * this->bytes_per_sample_; }
|
59
|
+
|
60
|
+
/// @brief Converts duration to frames.
|
61
|
+
/// @param ms Duration in milliseconds
|
62
|
+
/// @return Audio frames that will store `ms` milliseconds of audio. May round down for certain sample rates.
|
63
|
+
uint32_t ms_to_frames(uint32_t ms) const { return (ms * this->sample_rate_) / 1000; }
|
64
|
+
|
65
|
+
/// @brief Converts duration to samples.
|
66
|
+
/// @param ms Duration in milliseconds
|
67
|
+
/// @return Audio samples that will store `ms` milliseconds of audio. May round down for certain sample rates.
|
68
|
+
uint32_t ms_to_samples(uint32_t ms) const { return (ms * this->channels_ * this->sample_rate_) / 1000; }
|
69
|
+
|
70
|
+
/// @brief Converts duration to bytes. May round down for certain sample rates.
|
71
|
+
/// @param ms Duration in milliseconds
|
72
|
+
/// @return Bytes that will store `ms` milliseconds of audio. May round down for certain sample rates.
|
73
|
+
size_t ms_to_bytes(uint32_t ms) const {
|
74
|
+
return (ms * this->bytes_per_sample_ * this->channels_ * this->sample_rate_) / 1000;
|
12
75
|
}
|
76
|
+
|
77
|
+
/// @brief Computes the duration, in microseconds, the given amount of frames represents.
|
78
|
+
/// @param frames Number of audio frames
|
79
|
+
/// @return Duration in microseconds `frames` respresents. May be slightly inaccurate due to integer divison rounding
|
80
|
+
/// for certain sample rates.
|
81
|
+
uint32_t frames_to_microseconds(uint32_t frames) const;
|
82
|
+
|
83
|
+
/// @brief Computes the duration, in milliseconds, the given amount of frames represents. Avoids
|
84
|
+
/// accumulating rounding errors by updating `frames` with the remainder after converting.
|
85
|
+
/// @param frames Pointer to uint32_t with the number of audio frames. Replaced with the remainder.
|
86
|
+
/// @return Duration in milliseconds `frames` represents. Always less than or equal to the actual value due to
|
87
|
+
/// rounding.
|
88
|
+
uint32_t frames_to_milliseconds_with_remainder(uint32_t *frames) const;
|
89
|
+
|
90
|
+
// Class comparison operators
|
91
|
+
bool operator==(const AudioStreamInfo &rhs) const;
|
13
92
|
bool operator!=(const AudioStreamInfo &rhs) const { return !operator==(rhs); }
|
14
|
-
|
15
|
-
|
16
|
-
uint8_t
|
17
|
-
|
93
|
+
|
94
|
+
protected:
|
95
|
+
uint8_t bits_per_sample_;
|
96
|
+
uint8_t channels_;
|
97
|
+
uint32_t sample_rate_;
|
98
|
+
|
99
|
+
// The greatest common divisor between 1000 ms = 1 second and the sample rate. Used to avoid accumulating error when
|
100
|
+
// converting from frames to duration. Computed at construction.
|
101
|
+
uint32_t ms_sample_rate_gcd_;
|
102
|
+
|
103
|
+
// Conversion factor derived from the number of bits per sample. Assumes audio data is aligned to the byte. Computed
|
104
|
+
// at construction.
|
105
|
+
size_t bytes_per_sample_;
|
18
106
|
};
|
19
107
|
|
108
|
+
enum class AudioFileType : uint8_t {
|
109
|
+
NONE = 0,
|
110
|
+
#ifdef USE_AUDIO_FLAC_SUPPORT
|
111
|
+
FLAC,
|
112
|
+
#endif
|
113
|
+
#ifdef USE_AUDIO_MP3_SUPPORT
|
114
|
+
MP3,
|
115
|
+
#endif
|
116
|
+
WAV,
|
117
|
+
};
|
118
|
+
|
119
|
+
struct AudioFile {
|
120
|
+
const uint8_t *data;
|
121
|
+
size_t length;
|
122
|
+
AudioFileType file_type;
|
123
|
+
};
|
124
|
+
|
125
|
+
/// @brief Helper function to convert file type to a const char string
|
126
|
+
/// @param file_type
|
127
|
+
/// @return const char pointer to the readable file type
|
128
|
+
const char *audio_file_type_to_string(AudioFileType file_type);
|
129
|
+
|
130
|
+
/// @brief Scales Q15 fixed point audio samples. Scales in place if audio_samples == output_buffer.
|
131
|
+
/// @param audio_samples PCM int16 audio samples
|
132
|
+
/// @param output_buffer Buffer to store the scaled samples
|
133
|
+
/// @param scale_factor Q15 fixed point scaling factor
|
134
|
+
/// @param samples_to_scale Number of samples to scale
|
135
|
+
void scale_audio_samples(const int16_t *audio_samples, int16_t *output_buffer, int16_t scale_factor,
|
136
|
+
size_t samples_to_scale);
|
137
|
+
|
20
138
|
} // namespace audio
|
21
139
|
} // namespace esphome
|
@@ -0,0 +1,361 @@
|
|
1
|
+
#include "audio_decoder.h"
|
2
|
+
|
3
|
+
#ifdef USE_ESP32
|
4
|
+
|
5
|
+
#include "esphome/core/hal.h"
|
6
|
+
|
7
|
+
namespace esphome {
|
8
|
+
namespace audio {
|
9
|
+
|
10
|
+
static const uint32_t DECODING_TIMEOUT_MS = 50; // The decode function will yield after this duration
|
11
|
+
static const uint32_t READ_WRITE_TIMEOUT_MS = 20; // Timeout for transferring audio data
|
12
|
+
|
13
|
+
static const uint32_t MAX_POTENTIALLY_FAILED_COUNT = 10;
|
14
|
+
|
15
|
+
AudioDecoder::AudioDecoder(size_t input_buffer_size, size_t output_buffer_size) {
|
16
|
+
this->input_transfer_buffer_ = AudioSourceTransferBuffer::create(input_buffer_size);
|
17
|
+
this->output_transfer_buffer_ = AudioSinkTransferBuffer::create(output_buffer_size);
|
18
|
+
}
|
19
|
+
|
20
|
+
AudioDecoder::~AudioDecoder() {
|
21
|
+
#ifdef USE_AUDIO_MP3_SUPPORT
|
22
|
+
if (this->audio_file_type_ == AudioFileType::MP3) {
|
23
|
+
esp_audio_libs::helix_decoder::MP3FreeDecoder(this->mp3_decoder_);
|
24
|
+
}
|
25
|
+
#endif
|
26
|
+
}
|
27
|
+
|
28
|
+
esp_err_t AudioDecoder::add_source(std::weak_ptr<RingBuffer> &input_ring_buffer) {
|
29
|
+
if (this->input_transfer_buffer_ != nullptr) {
|
30
|
+
this->input_transfer_buffer_->set_source(input_ring_buffer);
|
31
|
+
return ESP_OK;
|
32
|
+
}
|
33
|
+
return ESP_ERR_NO_MEM;
|
34
|
+
}
|
35
|
+
|
36
|
+
esp_err_t AudioDecoder::add_sink(std::weak_ptr<RingBuffer> &output_ring_buffer) {
|
37
|
+
if (this->output_transfer_buffer_ != nullptr) {
|
38
|
+
this->output_transfer_buffer_->set_sink(output_ring_buffer);
|
39
|
+
return ESP_OK;
|
40
|
+
}
|
41
|
+
return ESP_ERR_NO_MEM;
|
42
|
+
}
|
43
|
+
|
44
|
+
#ifdef USE_SPEAKER
|
45
|
+
esp_err_t AudioDecoder::add_sink(speaker::Speaker *speaker) {
|
46
|
+
if (this->output_transfer_buffer_ != nullptr) {
|
47
|
+
this->output_transfer_buffer_->set_sink(speaker);
|
48
|
+
return ESP_OK;
|
49
|
+
}
|
50
|
+
return ESP_ERR_NO_MEM;
|
51
|
+
}
|
52
|
+
#endif
|
53
|
+
|
54
|
+
esp_err_t AudioDecoder::start(AudioFileType audio_file_type) {
|
55
|
+
if ((this->input_transfer_buffer_ == nullptr) || (this->output_transfer_buffer_ == nullptr)) {
|
56
|
+
return ESP_ERR_NO_MEM;
|
57
|
+
}
|
58
|
+
|
59
|
+
this->audio_file_type_ = audio_file_type;
|
60
|
+
|
61
|
+
this->potentially_failed_count_ = 0;
|
62
|
+
this->end_of_file_ = false;
|
63
|
+
|
64
|
+
switch (this->audio_file_type_) {
|
65
|
+
#ifdef USE_AUDIO_FLAC_SUPPORT
|
66
|
+
case AudioFileType::FLAC:
|
67
|
+
this->flac_decoder_ = make_unique<esp_audio_libs::flac::FLACDecoder>();
|
68
|
+
this->free_buffer_required_ =
|
69
|
+
this->output_transfer_buffer_->capacity(); // We'll revise this after reading the header
|
70
|
+
break;
|
71
|
+
#endif
|
72
|
+
#ifdef USE_AUDIO_MP3_SUPPORT
|
73
|
+
case AudioFileType::MP3:
|
74
|
+
this->mp3_decoder_ = esp_audio_libs::helix_decoder::MP3InitDecoder();
|
75
|
+
this->free_buffer_required_ = 1152 * sizeof(int16_t) * 2; // samples * size per sample * channels
|
76
|
+
break;
|
77
|
+
#endif
|
78
|
+
case AudioFileType::WAV:
|
79
|
+
this->wav_decoder_ = make_unique<esp_audio_libs::wav_decoder::WAVDecoder>();
|
80
|
+
this->wav_decoder_->reset();
|
81
|
+
this->free_buffer_required_ = 1024;
|
82
|
+
break;
|
83
|
+
case AudioFileType::NONE:
|
84
|
+
default:
|
85
|
+
return ESP_ERR_NOT_SUPPORTED;
|
86
|
+
break;
|
87
|
+
}
|
88
|
+
|
89
|
+
return ESP_OK;
|
90
|
+
}
|
91
|
+
|
92
|
+
AudioDecoderState AudioDecoder::decode(bool stop_gracefully) {
|
93
|
+
if (stop_gracefully) {
|
94
|
+
if (this->output_transfer_buffer_->available() == 0) {
|
95
|
+
if (this->end_of_file_) {
|
96
|
+
// The file decoder indicates it reached the end of file
|
97
|
+
return AudioDecoderState::FINISHED;
|
98
|
+
}
|
99
|
+
|
100
|
+
if (!this->input_transfer_buffer_->has_buffered_data()) {
|
101
|
+
// If all the internal buffers are empty, the decoding is done
|
102
|
+
return AudioDecoderState::FINISHED;
|
103
|
+
}
|
104
|
+
}
|
105
|
+
}
|
106
|
+
|
107
|
+
if (this->potentially_failed_count_ > MAX_POTENTIALLY_FAILED_COUNT) {
|
108
|
+
if (stop_gracefully) {
|
109
|
+
// No more new data is going to come in, so decoding is done
|
110
|
+
return AudioDecoderState::FINISHED;
|
111
|
+
}
|
112
|
+
return AudioDecoderState::FAILED;
|
113
|
+
}
|
114
|
+
|
115
|
+
FileDecoderState state = FileDecoderState::MORE_TO_PROCESS;
|
116
|
+
|
117
|
+
uint32_t decoding_start = millis();
|
118
|
+
|
119
|
+
while (state == FileDecoderState::MORE_TO_PROCESS) {
|
120
|
+
// Transfer decoded out
|
121
|
+
if (!this->pause_output_) {
|
122
|
+
size_t bytes_written = this->output_transfer_buffer_->transfer_data_to_sink(pdMS_TO_TICKS(READ_WRITE_TIMEOUT_MS));
|
123
|
+
if (this->audio_stream_info_.has_value()) {
|
124
|
+
this->accumulated_frames_written_ += this->audio_stream_info_.value().bytes_to_frames(bytes_written);
|
125
|
+
this->playback_ms_ +=
|
126
|
+
this->audio_stream_info_.value().frames_to_milliseconds_with_remainder(&this->accumulated_frames_written_);
|
127
|
+
}
|
128
|
+
} else {
|
129
|
+
// If paused, block to avoid wasting CPU resources
|
130
|
+
delay(READ_WRITE_TIMEOUT_MS);
|
131
|
+
}
|
132
|
+
|
133
|
+
// Verify there is enough space to store more decoded audio and that the function hasn't been running too long
|
134
|
+
if ((this->output_transfer_buffer_->free() < this->free_buffer_required_) ||
|
135
|
+
(millis() - decoding_start > DECODING_TIMEOUT_MS)) {
|
136
|
+
return AudioDecoderState::DECODING;
|
137
|
+
}
|
138
|
+
|
139
|
+
// Decode more audio
|
140
|
+
|
141
|
+
size_t bytes_read = this->input_transfer_buffer_->transfer_data_from_source(pdMS_TO_TICKS(READ_WRITE_TIMEOUT_MS));
|
142
|
+
|
143
|
+
if ((this->potentially_failed_count_ > 0) && (bytes_read == 0)) {
|
144
|
+
// Failed to decode in last attempt and there is no new data
|
145
|
+
|
146
|
+
if (this->input_transfer_buffer_->free() == 0) {
|
147
|
+
// The input buffer is full. Since it previously failed on the exact same data, we can never recover
|
148
|
+
state = FileDecoderState::FAILED;
|
149
|
+
} else {
|
150
|
+
// Attempt to get more data next time
|
151
|
+
state = FileDecoderState::IDLE;
|
152
|
+
}
|
153
|
+
} else if (this->input_transfer_buffer_->available() == 0) {
|
154
|
+
// No data to decode, attempt to get more data next time
|
155
|
+
state = FileDecoderState::IDLE;
|
156
|
+
} else {
|
157
|
+
switch (this->audio_file_type_) {
|
158
|
+
#ifdef USE_AUDIO_FLAC_SUPPORT
|
159
|
+
case AudioFileType::FLAC:
|
160
|
+
state = this->decode_flac_();
|
161
|
+
break;
|
162
|
+
#endif
|
163
|
+
#ifdef USE_AUDIO_MP3_SUPPORT
|
164
|
+
case AudioFileType::MP3:
|
165
|
+
state = this->decode_mp3_();
|
166
|
+
break;
|
167
|
+
#endif
|
168
|
+
case AudioFileType::WAV:
|
169
|
+
state = this->decode_wav_();
|
170
|
+
break;
|
171
|
+
case AudioFileType::NONE:
|
172
|
+
default:
|
173
|
+
state = FileDecoderState::IDLE;
|
174
|
+
break;
|
175
|
+
}
|
176
|
+
}
|
177
|
+
|
178
|
+
if (state == FileDecoderState::POTENTIALLY_FAILED) {
|
179
|
+
++this->potentially_failed_count_;
|
180
|
+
} else if (state == FileDecoderState::END_OF_FILE) {
|
181
|
+
this->end_of_file_ = true;
|
182
|
+
} else if (state == FileDecoderState::FAILED) {
|
183
|
+
return AudioDecoderState::FAILED;
|
184
|
+
} else if (state == FileDecoderState::MORE_TO_PROCESS) {
|
185
|
+
this->potentially_failed_count_ = 0;
|
186
|
+
}
|
187
|
+
}
|
188
|
+
return AudioDecoderState::DECODING;
|
189
|
+
}
|
190
|
+
|
191
|
+
#ifdef USE_AUDIO_FLAC_SUPPORT
|
192
|
+
FileDecoderState AudioDecoder::decode_flac_() {
|
193
|
+
if (!this->audio_stream_info_.has_value()) {
|
194
|
+
// Header hasn't been read
|
195
|
+
auto result = this->flac_decoder_->read_header(this->input_transfer_buffer_->get_buffer_start(),
|
196
|
+
this->input_transfer_buffer_->available());
|
197
|
+
|
198
|
+
if (result == esp_audio_libs::flac::FLAC_DECODER_HEADER_OUT_OF_DATA) {
|
199
|
+
return FileDecoderState::POTENTIALLY_FAILED;
|
200
|
+
}
|
201
|
+
|
202
|
+
if (result != esp_audio_libs::flac::FLAC_DECODER_SUCCESS) {
|
203
|
+
// Couldn't read FLAC header
|
204
|
+
return FileDecoderState::FAILED;
|
205
|
+
}
|
206
|
+
|
207
|
+
size_t bytes_consumed = this->flac_decoder_->get_bytes_index();
|
208
|
+
this->input_transfer_buffer_->decrease_buffer_length(bytes_consumed);
|
209
|
+
|
210
|
+
this->free_buffer_required_ = flac_decoder_->get_output_buffer_size_bytes();
|
211
|
+
if (this->output_transfer_buffer_->capacity() < this->free_buffer_required_) {
|
212
|
+
// Output buffer is not big enough
|
213
|
+
if (!this->output_transfer_buffer_->reallocate(this->free_buffer_required_)) {
|
214
|
+
// Couldn't reallocate output buffer
|
215
|
+
return FileDecoderState::FAILED;
|
216
|
+
}
|
217
|
+
}
|
218
|
+
|
219
|
+
this->audio_stream_info_ =
|
220
|
+
audio::AudioStreamInfo(this->flac_decoder_->get_sample_depth(), this->flac_decoder_->get_num_channels(),
|
221
|
+
this->flac_decoder_->get_sample_rate());
|
222
|
+
|
223
|
+
return FileDecoderState::MORE_TO_PROCESS;
|
224
|
+
}
|
225
|
+
|
226
|
+
uint32_t output_samples = 0;
|
227
|
+
auto result = this->flac_decoder_->decode_frame(
|
228
|
+
this->input_transfer_buffer_->get_buffer_start(), this->input_transfer_buffer_->available(),
|
229
|
+
reinterpret_cast<int16_t *>(this->output_transfer_buffer_->get_buffer_end()), &output_samples);
|
230
|
+
|
231
|
+
if (result == esp_audio_libs::flac::FLAC_DECODER_ERROR_OUT_OF_DATA) {
|
232
|
+
// Not an issue, just needs more data that we'll get next time.
|
233
|
+
return FileDecoderState::POTENTIALLY_FAILED;
|
234
|
+
}
|
235
|
+
|
236
|
+
size_t bytes_consumed = this->flac_decoder_->get_bytes_index();
|
237
|
+
this->input_transfer_buffer_->decrease_buffer_length(bytes_consumed);
|
238
|
+
|
239
|
+
if (result > esp_audio_libs::flac::FLAC_DECODER_ERROR_OUT_OF_DATA) {
|
240
|
+
// Corrupted frame, don't retry with current buffer content, wait for new sync
|
241
|
+
return FileDecoderState::POTENTIALLY_FAILED;
|
242
|
+
}
|
243
|
+
|
244
|
+
// We have successfully decoded some input data and have new output data
|
245
|
+
this->output_transfer_buffer_->increase_buffer_length(
|
246
|
+
this->audio_stream_info_.value().samples_to_bytes(output_samples));
|
247
|
+
|
248
|
+
if (result == esp_audio_libs::flac::FLAC_DECODER_NO_MORE_FRAMES) {
|
249
|
+
return FileDecoderState::END_OF_FILE;
|
250
|
+
}
|
251
|
+
|
252
|
+
return FileDecoderState::MORE_TO_PROCESS;
|
253
|
+
}
|
254
|
+
#endif
|
255
|
+
|
256
|
+
#ifdef USE_AUDIO_MP3_SUPPORT
|
257
|
+
FileDecoderState AudioDecoder::decode_mp3_() {
|
258
|
+
// Look for the next sync word
|
259
|
+
int buffer_length = (int) this->input_transfer_buffer_->available();
|
260
|
+
int32_t offset =
|
261
|
+
esp_audio_libs::helix_decoder::MP3FindSyncWord(this->input_transfer_buffer_->get_buffer_start(), buffer_length);
|
262
|
+
|
263
|
+
if (offset < 0) {
|
264
|
+
// New data may have the sync word
|
265
|
+
this->input_transfer_buffer_->decrease_buffer_length(buffer_length);
|
266
|
+
return FileDecoderState::POTENTIALLY_FAILED;
|
267
|
+
}
|
268
|
+
|
269
|
+
// Advance read pointer to match the offset for the syncword
|
270
|
+
this->input_transfer_buffer_->decrease_buffer_length(offset);
|
271
|
+
uint8_t *buffer_start = this->input_transfer_buffer_->get_buffer_start();
|
272
|
+
|
273
|
+
buffer_length = (int) this->input_transfer_buffer_->available();
|
274
|
+
int err = esp_audio_libs::helix_decoder::MP3Decode(this->mp3_decoder_, &buffer_start, &buffer_length,
|
275
|
+
(int16_t *) this->output_transfer_buffer_->get_buffer_end(), 0);
|
276
|
+
|
277
|
+
size_t consumed = this->input_transfer_buffer_->available() - buffer_length;
|
278
|
+
this->input_transfer_buffer_->decrease_buffer_length(consumed);
|
279
|
+
|
280
|
+
if (err) {
|
281
|
+
switch (err) {
|
282
|
+
case esp_audio_libs::helix_decoder::ERR_MP3_OUT_OF_MEMORY:
|
283
|
+
// Intentional fallthrough
|
284
|
+
case esp_audio_libs::helix_decoder::ERR_MP3_NULL_POINTER:
|
285
|
+
return FileDecoderState::FAILED;
|
286
|
+
break;
|
287
|
+
default:
|
288
|
+
// Most errors are recoverable by moving on to the next frame, so mark as potentailly failed
|
289
|
+
return FileDecoderState::POTENTIALLY_FAILED;
|
290
|
+
break;
|
291
|
+
}
|
292
|
+
} else {
|
293
|
+
esp_audio_libs::helix_decoder::MP3FrameInfo mp3_frame_info;
|
294
|
+
esp_audio_libs::helix_decoder::MP3GetLastFrameInfo(this->mp3_decoder_, &mp3_frame_info);
|
295
|
+
if (mp3_frame_info.outputSamps > 0) {
|
296
|
+
int bytes_per_sample = (mp3_frame_info.bitsPerSample / 8);
|
297
|
+
this->output_transfer_buffer_->increase_buffer_length(mp3_frame_info.outputSamps * bytes_per_sample);
|
298
|
+
|
299
|
+
if (!this->audio_stream_info_.has_value()) {
|
300
|
+
this->audio_stream_info_ =
|
301
|
+
audio::AudioStreamInfo(mp3_frame_info.bitsPerSample, mp3_frame_info.nChans, mp3_frame_info.samprate);
|
302
|
+
}
|
303
|
+
}
|
304
|
+
}
|
305
|
+
|
306
|
+
return FileDecoderState::MORE_TO_PROCESS;
|
307
|
+
}
|
308
|
+
#endif
|
309
|
+
|
310
|
+
FileDecoderState AudioDecoder::decode_wav_() {
|
311
|
+
if (!this->audio_stream_info_.has_value()) {
|
312
|
+
// Header hasn't been processed
|
313
|
+
|
314
|
+
esp_audio_libs::wav_decoder::WAVDecoderResult result = this->wav_decoder_->decode_header(
|
315
|
+
this->input_transfer_buffer_->get_buffer_start(), this->input_transfer_buffer_->available());
|
316
|
+
|
317
|
+
if (result == esp_audio_libs::wav_decoder::WAV_DECODER_SUCCESS_IN_DATA) {
|
318
|
+
this->input_transfer_buffer_->decrease_buffer_length(this->wav_decoder_->bytes_processed());
|
319
|
+
|
320
|
+
this->audio_stream_info_ = audio::AudioStreamInfo(
|
321
|
+
this->wav_decoder_->bits_per_sample(), this->wav_decoder_->num_channels(), this->wav_decoder_->sample_rate());
|
322
|
+
|
323
|
+
this->wav_bytes_left_ = this->wav_decoder_->chunk_bytes_left();
|
324
|
+
this->wav_has_known_end_ = (this->wav_bytes_left_ > 0);
|
325
|
+
return FileDecoderState::MORE_TO_PROCESS;
|
326
|
+
} else if (result == esp_audio_libs::wav_decoder::WAV_DECODER_WARNING_INCOMPLETE_DATA) {
|
327
|
+
// Available data didn't have the full header
|
328
|
+
return FileDecoderState::POTENTIALLY_FAILED;
|
329
|
+
} else {
|
330
|
+
return FileDecoderState::FAILED;
|
331
|
+
}
|
332
|
+
} else {
|
333
|
+
if (!this->wav_has_known_end_ || (this->wav_bytes_left_ > 0)) {
|
334
|
+
size_t bytes_to_copy = this->input_transfer_buffer_->available();
|
335
|
+
|
336
|
+
if (this->wav_has_known_end_) {
|
337
|
+
bytes_to_copy = std::min(bytes_to_copy, this->wav_bytes_left_);
|
338
|
+
}
|
339
|
+
|
340
|
+
bytes_to_copy = std::min(bytes_to_copy, this->output_transfer_buffer_->free());
|
341
|
+
|
342
|
+
if (bytes_to_copy > 0) {
|
343
|
+
std::memcpy(this->output_transfer_buffer_->get_buffer_end(), this->input_transfer_buffer_->get_buffer_start(),
|
344
|
+
bytes_to_copy);
|
345
|
+
this->input_transfer_buffer_->decrease_buffer_length(bytes_to_copy);
|
346
|
+
this->output_transfer_buffer_->increase_buffer_length(bytes_to_copy);
|
347
|
+
if (this->wav_has_known_end_) {
|
348
|
+
this->wav_bytes_left_ -= bytes_to_copy;
|
349
|
+
}
|
350
|
+
}
|
351
|
+
return FileDecoderState::IDLE;
|
352
|
+
}
|
353
|
+
}
|
354
|
+
|
355
|
+
return FileDecoderState::END_OF_FILE;
|
356
|
+
}
|
357
|
+
|
358
|
+
} // namespace audio
|
359
|
+
} // namespace esphome
|
360
|
+
|
361
|
+
#endif
|
@@ -0,0 +1,135 @@
|
|
1
|
+
#pragma once
|
2
|
+
|
3
|
+
#ifdef USE_ESP32
|
4
|
+
|
5
|
+
#include "audio.h"
|
6
|
+
#include "audio_transfer_buffer.h"
|
7
|
+
|
8
|
+
#include "esphome/core/defines.h"
|
9
|
+
#include "esphome/core/helpers.h"
|
10
|
+
#include "esphome/core/ring_buffer.h"
|
11
|
+
|
12
|
+
#ifdef USE_SPEAKER
|
13
|
+
#include "esphome/components/speaker/speaker.h"
|
14
|
+
#endif
|
15
|
+
|
16
|
+
#include "esp_err.h"
|
17
|
+
|
18
|
+
// esp-audio-libs
|
19
|
+
#ifdef USE_AUDIO_FLAC_SUPPORT
|
20
|
+
#include <flac_decoder.h>
|
21
|
+
#endif
|
22
|
+
#ifdef USE_AUDIO_MP3_SUPPORT
|
23
|
+
#include <mp3_decoder.h>
|
24
|
+
#endif
|
25
|
+
#include <wav_decoder.h>
|
26
|
+
|
27
|
+
namespace esphome {
|
28
|
+
namespace audio {
|
29
|
+
|
30
|
+
enum class AudioDecoderState : uint8_t {
|
31
|
+
DECODING = 0, // More data is available to decode
|
32
|
+
FINISHED, // All file data has been decoded and transferred
|
33
|
+
FAILED, // Encountered an error
|
34
|
+
};
|
35
|
+
|
36
|
+
// Only used within the AudioDecoder class; conveys the state of the particular file type decoder
|
37
|
+
enum class FileDecoderState : uint8_t {
|
38
|
+
MORE_TO_PROCESS, // Successsfully read a file chunk and more data is available to decode
|
39
|
+
IDLE, // Not enough data to decode, waiting for more to be transferred
|
40
|
+
POTENTIALLY_FAILED, // Decoder encountered a potentially recoverable error if more file data is available
|
41
|
+
FAILED, // Decoder encoutnered an uncrecoverable error
|
42
|
+
END_OF_FILE, // The specific file decoder knows its the end of the file
|
43
|
+
};
|
44
|
+
|
45
|
+
class AudioDecoder {
|
46
|
+
/*
|
47
|
+
* @brief Class that facilitates decoding an audio file.
|
48
|
+
* The audio file is read from a ring buffer source, decoded, and sent to an audio sink (ring buffer or speaker
|
49
|
+
* component).
|
50
|
+
* Supports wav, flac, and mp3 formats.
|
51
|
+
*/
|
52
|
+
public:
|
53
|
+
/// @brief Allocates the input and output transfer buffers
|
54
|
+
/// @param input_buffer_size Size of the input transfer buffer in bytes.
|
55
|
+
/// @param output_buffer_size Size of the output transfer buffer in bytes.
|
56
|
+
AudioDecoder(size_t input_buffer_size, size_t output_buffer_size);
|
57
|
+
|
58
|
+
/// @brief Deallocates the MP3 decoder (the flac and wav decoders are deallocated automatically)
|
59
|
+
~AudioDecoder();
|
60
|
+
|
61
|
+
/// @brief Adds a source ring buffer for raw file data. Takes ownership of the ring buffer in a shared_ptr.
|
62
|
+
/// @param input_ring_buffer weak_ptr of a shared_ptr of the sink ring buffer to transfer ownership
|
63
|
+
/// @return ESP_OK if successsful, ESP_ERR_NO_MEM if the transfer buffer wasn't allocated
|
64
|
+
esp_err_t add_source(std::weak_ptr<RingBuffer> &input_ring_buffer);
|
65
|
+
|
66
|
+
/// @brief Adds a sink ring buffer for decoded audio. Takes ownership of the ring buffer in a shared_ptr.
|
67
|
+
/// @param output_ring_buffer weak_ptr of a shared_ptr of the sink ring buffer to transfer ownership
|
68
|
+
/// @return ESP_OK if successsful, ESP_ERR_NO_MEM if the transfer buffer wasn't allocated
|
69
|
+
esp_err_t add_sink(std::weak_ptr<RingBuffer> &output_ring_buffer);
|
70
|
+
|
71
|
+
#ifdef USE_SPEAKER
|
72
|
+
/// @brief Adds a sink speaker for decoded audio.
|
73
|
+
/// @param speaker pointer to speaker component
|
74
|
+
/// @return ESP_OK if successsful, ESP_ERR_NO_MEM if the transfer buffer wasn't allocated
|
75
|
+
esp_err_t add_sink(speaker::Speaker *speaker);
|
76
|
+
#endif
|
77
|
+
|
78
|
+
/// @brief Sets up decoding the file
|
79
|
+
/// @param audio_file_type AudioFileType of the file
|
80
|
+
/// @return ESP_OK if successful, ESP_ERR_NO_MEM if the transfer buffers fail to allocate, or ESP_ERR_NOT_SUPPORTED if
|
81
|
+
/// the format isn't supported.
|
82
|
+
esp_err_t start(AudioFileType audio_file_type);
|
83
|
+
|
84
|
+
/// @brief Decodes audio from the ring buffer source and writes to the sink.
|
85
|
+
/// @param stop_gracefully If true, it indicates the file source is finished. The decoder will decode all the
|
86
|
+
/// reamining data and then finish.
|
87
|
+
/// @return AudioDecoderState
|
88
|
+
AudioDecoderState decode(bool stop_gracefully);
|
89
|
+
|
90
|
+
/// @brief Gets the audio stream information, if it has been decoded from the files header
|
91
|
+
/// @return optional<AudioStreamInfo> with the audio information. If not available yet, returns no value.
|
92
|
+
const optional<audio::AudioStreamInfo> &get_audio_stream_info() const { return this->audio_stream_info_; }
|
93
|
+
|
94
|
+
/// @brief Returns the duration of audio (in milliseconds) decoded and sent to the sink
|
95
|
+
/// @return Duration of decoded audio in milliseconds
|
96
|
+
uint32_t get_playback_ms() const { return this->playback_ms_; }
|
97
|
+
|
98
|
+
/// @brief Pauses sending resampled audio to the sink. If paused, it will continue to process internal buffers.
|
99
|
+
/// @param pause_state If true, audio data is not sent to the sink.
|
100
|
+
void set_pause_output_state(bool pause_state) { this->pause_output_ = pause_state; }
|
101
|
+
|
102
|
+
protected:
|
103
|
+
std::unique_ptr<esp_audio_libs::wav_decoder::WAVDecoder> wav_decoder_;
|
104
|
+
#ifdef USE_AUDIO_FLAC_SUPPORT
|
105
|
+
FileDecoderState decode_flac_();
|
106
|
+
std::unique_ptr<esp_audio_libs::flac::FLACDecoder> flac_decoder_;
|
107
|
+
#endif
|
108
|
+
#ifdef USE_AUDIO_MP3_SUPPORT
|
109
|
+
FileDecoderState decode_mp3_();
|
110
|
+
esp_audio_libs::helix_decoder::HMP3Decoder mp3_decoder_;
|
111
|
+
#endif
|
112
|
+
FileDecoderState decode_wav_();
|
113
|
+
|
114
|
+
std::unique_ptr<AudioSourceTransferBuffer> input_transfer_buffer_;
|
115
|
+
std::unique_ptr<AudioSinkTransferBuffer> output_transfer_buffer_;
|
116
|
+
|
117
|
+
AudioFileType audio_file_type_{AudioFileType::NONE};
|
118
|
+
optional<AudioStreamInfo> audio_stream_info_{};
|
119
|
+
|
120
|
+
size_t free_buffer_required_{0};
|
121
|
+
size_t wav_bytes_left_{0};
|
122
|
+
|
123
|
+
uint32_t potentially_failed_count_{0};
|
124
|
+
bool end_of_file_{false};
|
125
|
+
bool wav_has_known_end_{false};
|
126
|
+
|
127
|
+
bool pause_output_{false};
|
128
|
+
|
129
|
+
uint32_t accumulated_frames_written_{0};
|
130
|
+
uint32_t playback_ms_{0};
|
131
|
+
};
|
132
|
+
} // namespace audio
|
133
|
+
} // namespace esphome
|
134
|
+
|
135
|
+
#endif
|