From faa61696e036cb26e44ef0e4a28400e0636c5ca3 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Thu, 30 Apr 2026 21:43:24 -0400 Subject: [PATCH] [sendspin] Use sendspin-cpp to v0.4.0 to reduce stuttering (#16178) --- esphome/components/sendspin/__init__.py | 30 ++++++++++++++++--- .../sendspin/media_source/__init__.py | 4 +++ esphome/idf_component.yml | 2 +- .../sendspin/common-media_source.yaml | 1 + 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/esphome/components/sendspin/__init__.py b/esphome/components/sendspin/__init__.py index 58687ae838..1348bf2bfc 100644 --- a/esphome/components/sendspin/__init__.py +++ b/esphome/components/sendspin/__init__.py @@ -24,6 +24,7 @@ CONF_SENDSPIN_ID = "sendspin_id" CONF_INITIAL_STATIC_DELAY = "initial_static_delay" CONF_FIXED_DELAY = "fixed_delay" +CONF_DECODE_MEMORY = "decode_memory" # sendspin-cpp library lives in the global `sendspin` namespace. sendspin_library_ns = cg.global_ns.namespace("sendspin") @@ -39,6 +40,18 @@ CODEC_FORMAT_UNSUPPORTED = SendspinCodecFormat.enum("UNSUPPORTED") AudioSupportedFormatObject = sendspin_library_ns.struct("AudioSupportedFormatObject") PlayerRoleConfig = sendspin_library_ns.struct("PlayerRoleConfig") +# MemoryLocation enum (from sendspin/types.h) controls SPIRAM-vs-internal-RAM placement +# preference for the player role's transfer buffers. +SendspinMemoryLocation = sendspin_library_ns.enum("MemoryLocation", is_class=True) + +MEMORY_PSRAM = "psram" +MEMORY_INTERNAL = "internal" +MEMORY_LOCATIONS = [MEMORY_PSRAM, MEMORY_INTERNAL] +MEMORY_LOCATION_ENUM = { + MEMORY_PSRAM: SendspinMemoryLocation.PREFER_EXTERNAL, + MEMORY_INTERNAL: SendspinMemoryLocation.PREFER_INTERNAL, +} + # Trailing underscore avoids clashing with sendspin-cpp's global `sendspin` namespace. # Analysis tools strip the trailing underscore (same pattern as `template_`). sendspin_ns = cg.esphome_ns.namespace("sendspin_") @@ -193,7 +206,7 @@ async def to_code(config: ConfigType) -> None: ) # sendspin-cpp library - esp32.add_idf_component(name="sendspin/sendspin-cpp", ref="0.3.1") + esp32.add_idf_component(name="sendspin/sendspin-cpp", ref="0.4.0") cg.add_define("USE_SENDSPIN", True) # for MDNS @@ -249,14 +262,23 @@ async def to_code(config: ConfigType) -> None: "CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY", True ) - player_config_struct = cg.StructInitializer( - PlayerRoleConfig, + # Library defaults: priority 18 (one above httpd_priority 17 so the decoder is not + # starved by the HTTP server during the initial encoded-audio burst at stream start), + # interpolation/decode buffer locations PREFER_EXTERNAL. + player_struct_fields = [ ("audio_formats", audio_format_structs), ("audio_buffer_capacity", player_cfg[CONF_BUFFER_SIZE]), ("fixed_delay_us", player_cfg[CONF_FIXED_DELAY]), ("initial_static_delay_ms", player_cfg[CONF_INITIAL_STATIC_DELAY]), ("psram_stack", psram_stack), - ("priority", 2), + ] + if (decode_memory := player_cfg.get(CONF_DECODE_MEMORY)) is not None: + player_struct_fields.append( + ("decode_buffer_location", MEMORY_LOCATION_ENUM[decode_memory]) + ) + player_config_struct = cg.StructInitializer( + PlayerRoleConfig, + *player_struct_fields, ) cg.add(var.set_player_config(player_config_struct)) else: diff --git a/esphome/components/sendspin/media_source/__init__.py b/esphome/components/sendspin/media_source/__init__.py index 6d61a8a636..f689ab01cb 100644 --- a/esphome/components/sendspin/media_source/__init__.py +++ b/esphome/components/sendspin/media_source/__init__.py @@ -13,9 +13,11 @@ from esphome.cpp_generator import MockObj, TemplateArgsType from esphome.types import ConfigType from .. import ( + CONF_DECODE_MEMORY, CONF_FIXED_DELAY, CONF_INITIAL_STATIC_DELAY, CONF_SENDSPIN_ID, + MEMORY_LOCATIONS, SendspinHub, _validate_task_stack_in_psram, register_player_config, @@ -57,6 +59,7 @@ def _register(config: ConfigType) -> ConfigType: CONF_INITIAL_STATIC_DELAY: config[CONF_INITIAL_STATIC_DELAY], CONF_FIXED_DELAY: config[CONF_FIXED_DELAY], CONF_TASK_STACK_IN_PSRAM: config.get(CONF_TASK_STACK_IN_PSRAM, False), + CONF_DECODE_MEMORY: config.get(CONF_DECODE_MEMORY), } ) return config @@ -82,6 +85,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_SAMPLE_RATE, default=48000): cv.int_range( min=16000, max=96000 ), + cv.Optional(CONF_DECODE_MEMORY): cv.one_of(*MEMORY_LOCATIONS, lower=True), } ), cv.only_on_esp32, diff --git a/esphome/idf_component.yml b/esphome/idf_component.yml index 757fcf9dd7..d02c3adb8f 100644 --- a/esphome/idf_component.yml +++ b/esphome/idf_component.yml @@ -92,6 +92,6 @@ dependencies: esp32async/asynctcp: version: 3.4.91 sendspin/sendspin-cpp: - version: 0.3.1 + version: 0.4.0 lvgl/lvgl: version: 9.5.0 diff --git a/tests/components/sendspin/common-media_source.yaml b/tests/components/sendspin/common-media_source.yaml index 4a7cd79c67..5b33a54647 100644 --- a/tests/components/sendspin/common-media_source.yaml +++ b/tests/components/sendspin/common-media_source.yaml @@ -7,3 +7,4 @@ media_source: initial_static_delay: 5ms static_delay_adjustable: true fixed_delay: 480us + decode_memory: internal