From f33d137669672fe909c3e4c625805f8190b4fc05 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Mon, 4 May 2026 19:45:11 -0400 Subject: [PATCH] [audio][media_player][speaker] WAV decoding is no longer always built (#16244) --- esphome/components/audio/__init__.py | 5 +++-- esphome/components/audio/audio.cpp | 6 ++++++ esphome/components/audio/audio.h | 2 ++ esphome/components/audio/audio_decoder.cpp | 6 ++++++ esphome/components/audio/audio_decoder.h | 18 +++++++++++++----- esphome/components/audio_file/__init__.py | 2 ++ esphome/components/media_player/__init__.py | 3 +++ .../speaker/media_player/__init__.py | 2 ++ .../media_player/speaker_media_player.cpp | 5 ++++- esphome/core/defines.h | 1 + 10 files changed, 42 insertions(+), 8 deletions(-) diff --git a/esphome/components/audio/__init__.py b/esphome/components/audio/__init__.py index 60ff40ea4b8..7bd7ba9768d 100644 --- a/esphome/components/audio/__init__.py +++ b/esphome/components/audio/__init__.py @@ -64,8 +64,7 @@ class AudioData: flac_support: bool = False mp3_support: bool = False opus_support: bool = False - # WAV defaults to True for backward compatibility; will become opt-in in a future release - wav_support: bool = True + wav_support: bool = False micro_decoder_support: bool = False flac: FlacOptions = field(default_factory=FlacOptions) mp3: Mp3Options = field(default_factory=Mp3Options) @@ -428,3 +427,5 @@ async def to_code(config): add_idf_sdkconfig_option( "CONFIG_OPUS_PSEUDOSTACK_SIZE", data.opus.pseudostack.size ) + if data.wav_support: + cg.add_define("USE_AUDIO_WAV_SUPPORT") diff --git a/esphome/components/audio/audio.cpp b/esphome/components/audio/audio.cpp index 3d675109e49..b977c4e9181 100644 --- a/esphome/components/audio/audio.cpp +++ b/esphome/components/audio/audio.cpp @@ -55,8 +55,10 @@ const char *audio_file_type_to_string(AudioFileType file_type) { case AudioFileType::OPUS: return "OPUS"; #endif +#ifdef USE_AUDIO_WAV_SUPPORT case AudioFileType::WAV: return "WAV"; +#endif default: return "unknown"; } @@ -71,9 +73,11 @@ AudioFileType detect_audio_file_type(const char *content_type, const char *url) return AudioFileType::MP3; } #endif +#ifdef USE_AUDIO_WAV_SUPPORT if (strcasecmp(content_type, "audio/wav") == 0) { return AudioFileType::WAV; } +#endif #ifdef USE_AUDIO_FLAC_SUPPORT if (strcasecmp(content_type, "audio/flac") == 0 || strcasecmp(content_type, "audio/x-flac") == 0) { return AudioFileType::FLAC; @@ -91,9 +95,11 @@ AudioFileType detect_audio_file_type(const char *content_type, const char *url) // Fallback to URL extension if (url != nullptr && url[0] != '\0') { +#ifdef USE_AUDIO_WAV_SUPPORT if (str_endswith_ignore_case(url, ".wav")) { return AudioFileType::WAV; } +#endif #ifdef USE_AUDIO_MP3_SUPPORT if (str_endswith_ignore_case(url, ".mp3")) { return AudioFileType::MP3; diff --git a/esphome/components/audio/audio.h b/esphome/components/audio/audio.h index d3b41a362f1..9259f0a3c65 100644 --- a/esphome/components/audio/audio.h +++ b/esphome/components/audio/audio.h @@ -116,7 +116,9 @@ enum class AudioFileType : uint8_t { #ifdef USE_AUDIO_OPUS_SUPPORT OPUS, #endif +#ifdef USE_AUDIO_WAV_SUPPORT WAV, +#endif }; struct AudioFile { diff --git a/esphome/components/audio/audio_decoder.cpp b/esphome/components/audio/audio_decoder.cpp index 65a4db4e10e..156704fb86b 100644 --- a/esphome/components/audio/audio_decoder.cpp +++ b/esphome/components/audio/audio_decoder.cpp @@ -98,6 +98,7 @@ esp_err_t AudioDecoder::start(AudioFileType audio_file_type) { this->decoder_buffers_internally_ = true; break; #endif +#ifdef USE_AUDIO_WAV_SUPPORT case AudioFileType::WAV: this->wav_decoder_ = make_unique(); this->wav_decoder_->reset(); @@ -109,6 +110,7 @@ esp_err_t AudioDecoder::start(AudioFileType audio_file_type) { this->output_transfer_buffer_->reallocate(this->free_buffer_required_); } break; +#endif case AudioFileType::NONE: default: return ESP_ERR_NOT_SUPPORTED; @@ -226,9 +228,11 @@ AudioDecoderState AudioDecoder::decode(bool stop_gracefully) { state = this->decode_opus_(); break; #endif +#ifdef USE_AUDIO_WAV_SUPPORT case AudioFileType::WAV: state = this->decode_wav_(); break; +#endif case AudioFileType::NONE: default: state = FileDecoderState::IDLE; @@ -395,6 +399,7 @@ FileDecoderState AudioDecoder::decode_opus_() { } #endif +#ifdef USE_AUDIO_WAV_SUPPORT FileDecoderState AudioDecoder::decode_wav_() { if (!this->audio_stream_info_.has_value()) { // Header hasn't been processed @@ -441,6 +446,7 @@ FileDecoderState AudioDecoder::decode_wav_() { return FileDecoderState::END_OF_FILE; } +#endif } // namespace audio } // namespace esphome diff --git a/esphome/components/audio/audio_decoder.h b/esphome/components/audio/audio_decoder.h index 4cbe8b67209..8769e0b38b7 100644 --- a/esphome/components/audio/audio_decoder.h +++ b/esphome/components/audio/audio_decoder.h @@ -15,9 +15,6 @@ #include "esp_err.h" -// esp-audio-libs -#include - // micro-flac #ifdef USE_AUDIO_FLAC_SUPPORT #include @@ -33,6 +30,11 @@ #include #endif +// esp-audio-libs +#ifdef USE_AUDIO_WAV_SUPPORT +#include +#endif + namespace esphome { namespace audio { @@ -56,7 +58,7 @@ class AudioDecoder { * @brief Class that facilitates decoding an audio file. * The audio file is read from a source (ring buffer or const data pointer), decoded, and sent to an audio sink * (ring buffer, speaker component, or callback). - * Supports wav, flac, mp3, and ogg opus formats. + * Supports flac, mp3, ogg opus, and wav formats (each enabled independently at compile time). */ public: /// @brief Allocates the output transfer buffer and stores the input buffer size for later use by add_source() @@ -119,7 +121,6 @@ class AudioDecoder { void set_pause_output_state(bool pause_state) { this->pause_output_ = pause_state; } protected: - std::unique_ptr wav_decoder_; #ifdef USE_AUDIO_FLAC_SUPPORT FileDecoderState decode_flac_(); std::unique_ptr flac_decoder_; @@ -132,7 +133,10 @@ class AudioDecoder { FileDecoderState decode_opus_(); std::unique_ptr opus_decoder_; #endif +#ifdef USE_AUDIO_WAV_SUPPORT FileDecoderState decode_wav_(); + std::unique_ptr wav_decoder_; +#endif std::unique_ptr input_buffer_; std::unique_ptr output_transfer_buffer_; @@ -142,14 +146,18 @@ class AudioDecoder { size_t input_buffer_size_{0}; size_t free_buffer_required_{0}; +#ifdef USE_AUDIO_WAV_SUPPORT size_t wav_bytes_left_{0}; +#endif uint32_t potentially_failed_count_{0}; uint32_t accumulated_frames_written_{0}; uint32_t playback_ms_{0}; bool end_of_file_{false}; +#ifdef USE_AUDIO_WAV_SUPPORT bool wav_has_known_end_{false}; +#endif bool decoder_buffers_internally_{false}; diff --git a/esphome/components/audio_file/__init__.py b/esphome/components/audio_file/__init__.py index 88be6db168a..b246633c31b 100644 --- a/esphome/components/audio_file/__init__.py +++ b/esphome/components/audio_file/__init__.py @@ -193,6 +193,8 @@ def _validate_supported_local_file(config: list[ConfigType]) -> list[ConfigType] audio.request_mp3_support() elif media_file_type_str == str(audio.AUDIO_FILE_TYPE_ENUM["OPUS"]): audio.request_opus_support() + elif media_file_type_str == str(audio.AUDIO_FILE_TYPE_ENUM["WAV"]): + audio.request_wav_support() return config diff --git a/esphome/components/media_player/__init__.py b/esphome/components/media_player/__init__.py index 0024e3b9658..aa1e88dca95 100644 --- a/esphome/components/media_player/__init__.py +++ b/esphome/components/media_player/__init__.py @@ -133,6 +133,7 @@ def request_codecs_for_format_configs( audio.request_flac_support() audio.request_mp3_support() audio.request_opus_support() + audio.request_wav_support() else: if "FLAC" in needed_formats: audio.request_flac_support() @@ -140,6 +141,8 @@ def request_codecs_for_format_configs( audio.request_mp3_support() if "OPUS" in needed_formats: audio.request_opus_support() + if "WAV" in needed_formats: + audio.request_wav_support() # Local config key constants diff --git a/esphome/components/speaker/media_player/__init__.py b/esphome/components/speaker/media_player/__init__.py index fbc83ef12f5..90d9309f460 100644 --- a/esphome/components/speaker/media_player/__init__.py +++ b/esphome/components/speaker/media_player/__init__.py @@ -210,6 +210,8 @@ def _final_validate(config): audio.request_mp3_support() elif fmt_name == "OPUS": audio.request_opus_support() + elif fmt_name == "WAV": + audio.request_wav_support() break return config diff --git a/esphome/components/speaker/media_player/speaker_media_player.cpp b/esphome/components/speaker/media_player/speaker_media_player.cpp index ab11a89c3f4..afd93b3f45c 100644 --- a/esphome/components/speaker/media_player/speaker_media_player.cpp +++ b/esphome/components/speaker/media_player/speaker_media_player.cpp @@ -17,9 +17,12 @@ namespace speaker { // - Each stream has an individual speaker component for output // - Each stream is handled by an ``AudioPipeline`` object with two parts/tasks // - ``AudioReader`` handles reading from an HTTP source or from a PROGMEM flash set at compile time -// - ``AudioDecoder`` handles decoding the audio file. All formats are limited to two channels and 16 bits per sample +// - ``AudioDecoder`` handles decoding the audio file. All formats are limited to two channels and 16 bits per +// sample. +// Each format is enabled independently at compile time: // - FLAC // - MP3 (based on the libhelix decoder) +// - Ogg Opus // - WAV // - Each task runs until it is done processing the file or it receives a stop command // - Inter-task communication uses a FreeRTOS Event Group diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 93f4307e129..85454d3cc00 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -180,6 +180,7 @@ #define USE_AUDIO_FLAC_SUPPORT #define USE_AUDIO_MP3_SUPPORT #define USE_AUDIO_OPUS_SUPPORT +#define USE_AUDIO_WAV_SUPPORT #define USE_API #define USE_API_CLIENT_CONNECTED_TRIGGER #define USE_API_CLIENT_DISCONNECTED_TRIGGER