mirror of
https://github.com/esphome/esphome.git
synced 2026-03-23 18:59:02 +08:00
[i2s_audio] Remove legacy I2S driver support (#14932)
This commit is contained in:
@@ -244,7 +244,6 @@ esphome/components/hyt271/* @Philippe12
|
||||
esphome/components/i2c/* @esphome/core
|
||||
esphome/components/i2c_device/* @gabest11
|
||||
esphome/components/i2s_audio/* @jesserockz
|
||||
esphome/components/i2s_audio/media_player/* @jesserockz
|
||||
esphome/components/i2s_audio/microphone/* @jesserockz
|
||||
esphome/components/i2s_audio/speaker/* @jesserockz @kahrendt
|
||||
esphome/components/iaqcore/* @yozik04
|
||||
|
||||
@@ -53,8 +53,6 @@ CONF_RIGHT = "right"
|
||||
CONF_STEREO = "stereo"
|
||||
CONF_BOTH = "both"
|
||||
|
||||
CONF_USE_LEGACY = "use_legacy"
|
||||
|
||||
i2s_audio_ns = cg.esphome_ns.namespace("i2s_audio")
|
||||
I2SAudioComponent = i2s_audio_ns.class_("I2SAudioComponent", cg.Component)
|
||||
I2SAudioBase = i2s_audio_ns.class_(
|
||||
@@ -154,20 +152,6 @@ def validate_mclk_divisible_by_3(config):
|
||||
return config
|
||||
|
||||
|
||||
# Key for storing legacy driver setting in CORE.data
|
||||
I2S_USE_LEGACY_DRIVER_KEY = "i2s_use_legacy_driver"
|
||||
|
||||
|
||||
def _get_use_legacy_driver():
|
||||
"""Get the legacy driver setting from CORE.data."""
|
||||
return CORE.data.get(I2S_USE_LEGACY_DRIVER_KEY)
|
||||
|
||||
|
||||
def _set_use_legacy_driver(value: bool) -> None:
|
||||
"""Set the legacy driver setting in CORE.data."""
|
||||
CORE.data[I2S_USE_LEGACY_DRIVER_KEY] = value
|
||||
|
||||
|
||||
def i2s_audio_component_schema(
|
||||
class_: MockObjClass,
|
||||
*,
|
||||
@@ -192,10 +176,6 @@ def i2s_audio_component_schema(
|
||||
*I2S_MODE_OPTIONS, lower=True
|
||||
),
|
||||
cv.Optional(CONF_USE_APLL, default=False): cv.boolean,
|
||||
cv.Optional(CONF_BITS_PER_CHANNEL, default="default"): cv.All(
|
||||
cv.Any(cv.float_with_unit("bits", "bit"), "default"),
|
||||
cv.one_of(*I2S_BITS_PER_CHANNEL),
|
||||
),
|
||||
cv.Optional(CONF_MCLK_MULTIPLE, default=256): cv.one_of(*I2S_MCLK_MULTIPLE),
|
||||
}
|
||||
)
|
||||
@@ -203,59 +183,28 @@ def i2s_audio_component_schema(
|
||||
|
||||
async def register_i2s_audio_component(var, config):
|
||||
await cg.register_parented(var, config[CONF_I2S_AUDIO_ID])
|
||||
if use_legacy():
|
||||
cg.add(var.set_i2s_mode(I2S_MODE_OPTIONS[config[CONF_I2S_MODE]]))
|
||||
cg.add(var.set_channel(I2S_CHANNELS[config[CONF_CHANNEL]]))
|
||||
cg.add(
|
||||
var.set_bits_per_sample(I2S_BITS_PER_SAMPLE[config[CONF_BITS_PER_SAMPLE]])
|
||||
)
|
||||
cg.add(
|
||||
var.set_bits_per_channel(
|
||||
I2S_BITS_PER_CHANNEL[config[CONF_BITS_PER_CHANNEL]]
|
||||
)
|
||||
)
|
||||
else:
|
||||
cg.add(var.set_i2s_role(I2S_ROLE_OPTIONS[config[CONF_I2S_MODE]]))
|
||||
slot_mode = config[CONF_CHANNEL]
|
||||
if slot_mode != CONF_STEREO:
|
||||
slot_mode = CONF_MONO
|
||||
slot_mask = config[CONF_CHANNEL]
|
||||
if slot_mask not in [CONF_LEFT, CONF_RIGHT]:
|
||||
slot_mask = CONF_BOTH
|
||||
cg.add(var.set_slot_mode(I2S_SLOT_MODE[slot_mode]))
|
||||
cg.add(var.set_std_slot_mask(I2S_STD_SLOT_MASK[slot_mask]))
|
||||
cg.add(var.set_slot_bit_width(I2S_SLOT_BIT_WIDTH[config[CONF_BITS_PER_SAMPLE]]))
|
||||
cg.add(var.set_i2s_role(I2S_ROLE_OPTIONS[config[CONF_I2S_MODE]]))
|
||||
slot_mode = config[CONF_CHANNEL]
|
||||
if slot_mode != CONF_STEREO:
|
||||
slot_mode = CONF_MONO
|
||||
slot_mask = config[CONF_CHANNEL]
|
||||
if slot_mask not in [CONF_LEFT, CONF_RIGHT]:
|
||||
slot_mask = CONF_BOTH
|
||||
cg.add(var.set_slot_mode(I2S_SLOT_MODE[slot_mode]))
|
||||
cg.add(var.set_std_slot_mask(I2S_STD_SLOT_MASK[slot_mask]))
|
||||
cg.add(var.set_slot_bit_width(I2S_SLOT_BIT_WIDTH[config[CONF_BITS_PER_SAMPLE]]))
|
||||
cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE]))
|
||||
cg.add(var.set_use_apll(config[CONF_USE_APLL]))
|
||||
cg.add(var.set_mclk_multiple(I2S_MCLK_MULTIPLE[config[CONF_MCLK_MULTIPLE]]))
|
||||
|
||||
|
||||
def validate_use_legacy(value):
|
||||
if CONF_USE_LEGACY in value:
|
||||
existing_value = _get_use_legacy_driver()
|
||||
if (existing_value is not None) and (existing_value != value[CONF_USE_LEGACY]):
|
||||
raise cv.Invalid(
|
||||
f"All i2s_audio components must set {CONF_USE_LEGACY} to the same value."
|
||||
)
|
||||
if (not value[CONF_USE_LEGACY]) and (CORE.using_arduino):
|
||||
raise cv.Invalid("Arduino supports only the legacy i2s driver")
|
||||
_set_use_legacy_driver(value[CONF_USE_LEGACY])
|
||||
elif CORE.using_arduino:
|
||||
_set_use_legacy_driver(True)
|
||||
return value
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(I2SAudioComponent),
|
||||
cv.Required(CONF_I2S_LRCLK_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_I2S_BCLK_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_I2S_MCLK_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_USE_LEGACY): cv.boolean,
|
||||
},
|
||||
),
|
||||
validate_use_legacy,
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(I2SAudioComponent),
|
||||
cv.Required(CONF_I2S_LRCLK_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_I2S_BCLK_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_I2S_MCLK_PIN): pins.internal_gpio_output_pin_number,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -311,13 +260,6 @@ def _assign_ports() -> None:
|
||||
|
||||
|
||||
def _final_validate(_):
|
||||
from esphome.components.esp32 import idf_version
|
||||
|
||||
if use_legacy() and idf_version() >= cv.Version(6, 0, 0):
|
||||
raise cv.Invalid(
|
||||
"The legacy I2S driver is not available in ESP-IDF 6.0+. "
|
||||
"Set 'use_legacy: false' in i2s_audio configuration."
|
||||
)
|
||||
i2s_audio_configs = fv.full_config.get()[CONF_I2S_AUDIO]
|
||||
variant = get_esp32_variant()
|
||||
if variant not in I2S_PORTS:
|
||||
@@ -329,10 +271,6 @@ def _final_validate(_):
|
||||
_assign_ports()
|
||||
|
||||
|
||||
def use_legacy():
|
||||
return _get_use_legacy_driver()
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
|
||||
@@ -349,11 +287,6 @@ async def to_code(config):
|
||||
# Re-enable ESP-IDF's I2S driver (excluded by default to save compile time)
|
||||
include_builtin_idf_component("esp_driver_i2s")
|
||||
|
||||
if use_legacy():
|
||||
cg.add_define("USE_I2S_LEGACY")
|
||||
# Legacy I2S API lives in the "driver" shim component (driver/i2s.h)
|
||||
include_builtin_idf_component("driver")
|
||||
|
||||
# Helps avoid callbacks being skipped due to processor load
|
||||
add_idf_sdkconfig_option("CONFIG_I2S_ISR_IRAM_SAFE", True)
|
||||
|
||||
|
||||
@@ -6,11 +6,7 @@
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include <esp_idf_version.h>
|
||||
#ifdef USE_I2S_LEGACY
|
||||
#include <driver/i2s.h>
|
||||
#else
|
||||
#include <driver/i2s_std.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace i2s_audio {
|
||||
@@ -19,33 +15,19 @@ class I2SAudioComponent;
|
||||
|
||||
class I2SAudioBase : public Parented<I2SAudioComponent> {
|
||||
public:
|
||||
#ifdef USE_I2S_LEGACY
|
||||
void set_i2s_mode(i2s_mode_t mode) { this->i2s_mode_ = mode; }
|
||||
void set_channel(i2s_channel_fmt_t channel) { this->channel_ = channel; }
|
||||
void set_bits_per_sample(i2s_bits_per_sample_t bits_per_sample) { this->bits_per_sample_ = bits_per_sample; }
|
||||
void set_bits_per_channel(i2s_bits_per_chan_t bits_per_channel) { this->bits_per_channel_ = bits_per_channel; }
|
||||
#else
|
||||
void set_i2s_role(i2s_role_t role) { this->i2s_role_ = role; }
|
||||
void set_slot_mode(i2s_slot_mode_t slot_mode) { this->slot_mode_ = slot_mode; }
|
||||
void set_std_slot_mask(i2s_std_slot_mask_t std_slot_mask) { this->std_slot_mask_ = std_slot_mask; }
|
||||
void set_slot_bit_width(i2s_slot_bit_width_t slot_bit_width) { this->slot_bit_width_ = slot_bit_width; }
|
||||
#endif
|
||||
void set_sample_rate(uint32_t sample_rate) { this->sample_rate_ = sample_rate; }
|
||||
void set_use_apll(uint32_t use_apll) { this->use_apll_ = use_apll; }
|
||||
void set_mclk_multiple(i2s_mclk_multiple_t mclk_multiple) { this->mclk_multiple_ = mclk_multiple; }
|
||||
|
||||
protected:
|
||||
#ifdef USE_I2S_LEGACY
|
||||
i2s_mode_t i2s_mode_{};
|
||||
i2s_channel_fmt_t channel_;
|
||||
i2s_bits_per_sample_t bits_per_sample_;
|
||||
i2s_bits_per_chan_t bits_per_channel_;
|
||||
#else
|
||||
i2s_role_t i2s_role_{};
|
||||
i2s_slot_mode_t slot_mode_;
|
||||
i2s_std_slot_mask_t std_slot_mask_;
|
||||
i2s_slot_bit_width_t slot_bit_width_;
|
||||
#endif
|
||||
uint32_t sample_rate_;
|
||||
bool use_apll_;
|
||||
i2s_mclk_multiple_t mclk_multiple_;
|
||||
@@ -57,17 +39,6 @@ class I2SAudioOut : public I2SAudioBase {};
|
||||
|
||||
class I2SAudioComponent : public Component {
|
||||
public:
|
||||
#ifdef USE_I2S_LEGACY
|
||||
i2s_pin_config_t get_pin_config() const {
|
||||
return {
|
||||
.mck_io_num = this->mclk_pin_,
|
||||
.bck_io_num = this->bclk_pin_,
|
||||
.ws_io_num = this->lrclk_pin_,
|
||||
.data_out_num = I2S_PIN_NO_CHANGE,
|
||||
.data_in_num = I2S_PIN_NO_CHANGE,
|
||||
};
|
||||
}
|
||||
#else
|
||||
i2s_std_gpio_config_t get_pin_config() const {
|
||||
return {.mclk = (gpio_num_t) this->mclk_pin_,
|
||||
.bclk = (gpio_num_t) this->bclk_pin_,
|
||||
@@ -80,7 +51,6 @@ class I2SAudioComponent : public Component {
|
||||
.ws_inv = false,
|
||||
}};
|
||||
}
|
||||
#endif
|
||||
|
||||
void set_mclk_pin(int pin) { this->mclk_pin_ = pin; }
|
||||
void set_bclk_pin(int pin) { this->bclk_pin_ = pin; }
|
||||
@@ -101,13 +71,8 @@ class I2SAudioComponent : public Component {
|
||||
|
||||
I2SAudioIn *audio_in_{nullptr};
|
||||
I2SAudioOut *audio_out_{nullptr};
|
||||
#ifdef USE_I2S_LEGACY
|
||||
int mclk_pin_{I2S_PIN_NO_CHANGE};
|
||||
int bclk_pin_{I2S_PIN_NO_CHANGE};
|
||||
#else
|
||||
int mclk_pin_{I2S_GPIO_UNUSED};
|
||||
int bclk_pin_{I2S_GPIO_UNUSED};
|
||||
#endif
|
||||
int lrclk_pin_;
|
||||
int port_{};
|
||||
};
|
||||
|
||||
@@ -1,121 +1,7 @@
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import esp32, media_player
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_MODE
|
||||
|
||||
from .. import (
|
||||
CONF_I2S_AUDIO_ID,
|
||||
CONF_I2S_DOUT_PIN,
|
||||
CONF_LEFT,
|
||||
CONF_MONO,
|
||||
CONF_RIGHT,
|
||||
CONF_STEREO,
|
||||
I2SAudioComponent,
|
||||
I2SAudioOut,
|
||||
i2s_audio_ns,
|
||||
use_legacy,
|
||||
CONFIG_SCHEMA = cv.invalid(
|
||||
"The I2S audio media player has been removed. "
|
||||
"Use the speaker media player component instead. "
|
||||
"See https://esphome.io/components/media_player/speaker.html for details."
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
DEPENDENCIES = ["i2s_audio"]
|
||||
|
||||
I2SAudioMediaPlayer = i2s_audio_ns.class_(
|
||||
"I2SAudioMediaPlayer", cg.Component, media_player.MediaPlayer, I2SAudioOut
|
||||
)
|
||||
|
||||
i2s_dac_mode_t = cg.global_ns.enum("i2s_dac_mode_t")
|
||||
|
||||
|
||||
CONF_MUTE_PIN = "mute_pin"
|
||||
CONF_AUDIO_ID = "audio_id"
|
||||
CONF_DAC_TYPE = "dac_type"
|
||||
CONF_I2S_COMM_FMT = "i2s_comm_fmt"
|
||||
|
||||
INTERNAL_DAC_OPTIONS = {
|
||||
CONF_LEFT: i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN,
|
||||
CONF_RIGHT: i2s_dac_mode_t.I2S_DAC_CHANNEL_RIGHT_EN,
|
||||
CONF_STEREO: i2s_dac_mode_t.I2S_DAC_CHANNEL_BOTH_EN,
|
||||
}
|
||||
|
||||
EXTERNAL_DAC_OPTIONS = [CONF_MONO, CONF_STEREO]
|
||||
|
||||
NO_INTERNAL_DAC_VARIANTS = [esp32.VARIANT_ESP32S2]
|
||||
|
||||
I2C_COMM_FMT_OPTIONS = ["lsb", "msb"]
|
||||
|
||||
|
||||
def validate_esp32_variant(config):
|
||||
if config[CONF_DAC_TYPE] != "internal":
|
||||
return config
|
||||
variant = esp32.get_esp32_variant()
|
||||
if variant in NO_INTERNAL_DAC_VARIANTS:
|
||||
raise cv.Invalid(f"{variant} does not have an internal DAC")
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.typed_schema(
|
||||
{
|
||||
"internal": media_player.media_player_schema(I2SAudioMediaPlayer)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent),
|
||||
cv.Required(CONF_MODE): cv.enum(INTERNAL_DAC_OPTIONS, lower=True),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA),
|
||||
"external": media_player.media_player_schema(I2SAudioMediaPlayer)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent),
|
||||
cv.Required(
|
||||
CONF_I2S_DOUT_PIN
|
||||
): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_MUTE_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_MODE, default="mono"): cv.one_of(
|
||||
*EXTERNAL_DAC_OPTIONS, lower=True
|
||||
),
|
||||
cv.Optional(CONF_I2S_COMM_FMT, default="msb"): cv.one_of(
|
||||
*I2C_COMM_FMT_OPTIONS, lower=True
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA),
|
||||
},
|
||||
key=CONF_DAC_TYPE,
|
||||
),
|
||||
cv.only_with_arduino,
|
||||
validate_esp32_variant,
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(_):
|
||||
if not use_legacy():
|
||||
raise cv.Invalid("I2S media player is only compatible with legacy i2s driver")
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = await media_player.new_media_player(config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
await cg.register_parented(var, config[CONF_I2S_AUDIO_ID])
|
||||
|
||||
if config[CONF_DAC_TYPE] == "internal":
|
||||
cg.add(var.set_internal_dac_mode(config[CONF_MODE]))
|
||||
else:
|
||||
cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN]))
|
||||
if CONF_MUTE_PIN in config:
|
||||
pin = await cg.gpio_pin_expression(config[CONF_MUTE_PIN])
|
||||
cg.add(var.set_mute_pin(pin))
|
||||
cg.add(var.set_external_dac_channels(2 if config[CONF_MODE] == "stereo" else 1))
|
||||
cg.add(var.set_i2s_comm_fmt_lsb(config[CONF_I2S_COMM_FMT] == "lsb"))
|
||||
|
||||
cg.add_library("WiFi", None)
|
||||
cg.add_library("NetworkClientSecure", None)
|
||||
cg.add_library("HTTPClient", None)
|
||||
cg.add_library("esphome/ESP32-audioI2S", "2.3.0")
|
||||
cg.add_build_flag("-DAUDIO_NO_SD_FS")
|
||||
|
||||
@@ -1,260 +0,0 @@
|
||||
#include "i2s_audio_media_player.h"
|
||||
|
||||
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace i2s_audio {
|
||||
|
||||
static const char *const TAG = "audio";
|
||||
|
||||
void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) {
|
||||
media_player::MediaPlayerState play_state = media_player::MEDIA_PLAYER_STATE_PLAYING;
|
||||
auto announcement = call.get_announcement();
|
||||
if (announcement.has_value()) {
|
||||
play_state = *announcement ? media_player::MEDIA_PLAYER_STATE_ANNOUNCING : media_player::MEDIA_PLAYER_STATE_PLAYING;
|
||||
}
|
||||
auto media_url = call.get_media_url();
|
||||
if (media_url.has_value()) {
|
||||
this->current_url_ = media_url;
|
||||
if (this->i2s_state_ != I2S_STATE_STOPPED && this->audio_ != nullptr) {
|
||||
if (this->audio_->isRunning()) {
|
||||
this->audio_->stopSong();
|
||||
}
|
||||
this->audio_->connecttohost(media_url->c_str());
|
||||
this->state = play_state;
|
||||
} else {
|
||||
this->start();
|
||||
}
|
||||
}
|
||||
|
||||
if (play_state == media_player::MEDIA_PLAYER_STATE_ANNOUNCING) {
|
||||
this->is_announcement_ = true;
|
||||
}
|
||||
|
||||
auto vol = call.get_volume();
|
||||
if (vol.has_value()) {
|
||||
this->volume = *vol;
|
||||
this->set_volume_(volume);
|
||||
this->unmute_();
|
||||
}
|
||||
auto cmd = call.get_command();
|
||||
if (cmd.has_value()) {
|
||||
switch (*cmd) {
|
||||
case media_player::MEDIA_PLAYER_COMMAND_MUTE:
|
||||
this->mute_();
|
||||
break;
|
||||
case media_player::MEDIA_PLAYER_COMMAND_UNMUTE:
|
||||
this->unmute_();
|
||||
break;
|
||||
case media_player::MEDIA_PLAYER_COMMAND_VOLUME_UP: {
|
||||
float new_volume = this->volume + 0.1f;
|
||||
if (new_volume > 1.0f)
|
||||
new_volume = 1.0f;
|
||||
this->set_volume_(new_volume);
|
||||
this->unmute_();
|
||||
break;
|
||||
}
|
||||
case media_player::MEDIA_PLAYER_COMMAND_VOLUME_DOWN: {
|
||||
float new_volume = this->volume - 0.1f;
|
||||
if (new_volume < 0.0f)
|
||||
new_volume = 0.0f;
|
||||
this->set_volume_(new_volume);
|
||||
this->unmute_();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (this->i2s_state_ != I2S_STATE_RUNNING) {
|
||||
return;
|
||||
}
|
||||
switch (*cmd) {
|
||||
case media_player::MEDIA_PLAYER_COMMAND_PLAY:
|
||||
if (!this->audio_->isRunning())
|
||||
this->audio_->pauseResume();
|
||||
this->state = play_state;
|
||||
break;
|
||||
case media_player::MEDIA_PLAYER_COMMAND_PAUSE:
|
||||
if (this->audio_->isRunning())
|
||||
this->audio_->pauseResume();
|
||||
this->state = media_player::MEDIA_PLAYER_STATE_PAUSED;
|
||||
break;
|
||||
case media_player::MEDIA_PLAYER_COMMAND_STOP:
|
||||
this->stop();
|
||||
break;
|
||||
case media_player::MEDIA_PLAYER_COMMAND_TOGGLE:
|
||||
this->audio_->pauseResume();
|
||||
if (this->audio_->isRunning()) {
|
||||
this->state = media_player::MEDIA_PLAYER_STATE_PLAYING;
|
||||
} else {
|
||||
this->state = media_player::MEDIA_PLAYER_STATE_PAUSED;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
this->publish_state();
|
||||
}
|
||||
|
||||
void I2SAudioMediaPlayer::mute_() {
|
||||
if (this->mute_pin_ != nullptr) {
|
||||
this->mute_pin_->digital_write(true);
|
||||
} else {
|
||||
this->set_volume_(0.0f, false);
|
||||
}
|
||||
this->muted_ = true;
|
||||
}
|
||||
void I2SAudioMediaPlayer::unmute_() {
|
||||
if (this->mute_pin_ != nullptr) {
|
||||
this->mute_pin_->digital_write(false);
|
||||
} else {
|
||||
this->set_volume_(this->volume, false);
|
||||
}
|
||||
this->muted_ = false;
|
||||
}
|
||||
void I2SAudioMediaPlayer::set_volume_(float volume, bool publish) {
|
||||
if (this->audio_ != nullptr)
|
||||
this->audio_->setVolume(remap<uint8_t, float>(volume, 0.0f, 1.0f, 0, 21));
|
||||
if (publish)
|
||||
this->volume = volume;
|
||||
}
|
||||
|
||||
void I2SAudioMediaPlayer::setup() { this->state = media_player::MEDIA_PLAYER_STATE_IDLE; }
|
||||
|
||||
void I2SAudioMediaPlayer::loop() {
|
||||
switch (this->i2s_state_) {
|
||||
case I2S_STATE_STARTING:
|
||||
this->start_();
|
||||
break;
|
||||
case I2S_STATE_RUNNING:
|
||||
this->play_();
|
||||
break;
|
||||
case I2S_STATE_STOPPING:
|
||||
this->stop_();
|
||||
break;
|
||||
case I2S_STATE_STOPPED:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void I2SAudioMediaPlayer::play_() {
|
||||
this->audio_->loop();
|
||||
if ((this->state == media_player::MEDIA_PLAYER_STATE_PLAYING ||
|
||||
this->state == media_player::MEDIA_PLAYER_STATE_ANNOUNCING) &&
|
||||
!this->audio_->isRunning()) {
|
||||
this->stop();
|
||||
}
|
||||
}
|
||||
|
||||
void I2SAudioMediaPlayer::start() { this->i2s_state_ = I2S_STATE_STARTING; }
|
||||
void I2SAudioMediaPlayer::start_() {
|
||||
if (!this->parent_->try_lock()) {
|
||||
return; // Waiting for another i2s to return lock
|
||||
}
|
||||
|
||||
#if SOC_I2S_SUPPORTS_DAC
|
||||
if (this->internal_dac_mode_ != I2S_DAC_CHANNEL_DISABLE) {
|
||||
this->audio_ = make_unique<Audio>(true, this->internal_dac_mode_, this->parent_->get_port());
|
||||
} else {
|
||||
#endif
|
||||
this->audio_ = make_unique<Audio>(false, 3, this->parent_->get_port());
|
||||
|
||||
i2s_pin_config_t pin_config = this->parent_->get_pin_config();
|
||||
pin_config.data_out_num = this->dout_pin_;
|
||||
i2s_set_pin(this->parent_->get_port(), &pin_config);
|
||||
|
||||
this->audio_->setI2SCommFMT_LSB(this->i2s_comm_fmt_lsb_);
|
||||
this->audio_->forceMono(this->external_dac_channels_ == 1);
|
||||
if (this->mute_pin_ != nullptr) {
|
||||
this->mute_pin_->setup();
|
||||
this->mute_pin_->digital_write(false);
|
||||
}
|
||||
#if SOC_I2S_SUPPORTS_DAC
|
||||
}
|
||||
#endif
|
||||
|
||||
this->i2s_state_ = I2S_STATE_RUNNING;
|
||||
this->high_freq_.start();
|
||||
this->audio_->setVolume(remap<uint8_t, float>(this->volume, 0.0f, 1.0f, 0, 21));
|
||||
if (this->current_url_.has_value()) {
|
||||
this->audio_->connecttohost(this->current_url_.value().c_str());
|
||||
this->state = media_player::MEDIA_PLAYER_STATE_PLAYING;
|
||||
if (this->is_announcement_) {
|
||||
this->state = media_player::MEDIA_PLAYER_STATE_ANNOUNCING;
|
||||
}
|
||||
this->publish_state();
|
||||
}
|
||||
}
|
||||
void I2SAudioMediaPlayer::stop() {
|
||||
if (this->i2s_state_ == I2S_STATE_STOPPED) {
|
||||
return;
|
||||
}
|
||||
if (this->i2s_state_ == I2S_STATE_STARTING) {
|
||||
this->i2s_state_ = I2S_STATE_STOPPED;
|
||||
return;
|
||||
}
|
||||
this->i2s_state_ = I2S_STATE_STOPPING;
|
||||
}
|
||||
void I2SAudioMediaPlayer::stop_() {
|
||||
if (this->audio_->isRunning()) {
|
||||
this->audio_->stopSong();
|
||||
return;
|
||||
}
|
||||
|
||||
this->audio_ = nullptr;
|
||||
this->current_url_ = {};
|
||||
this->parent_->unlock();
|
||||
this->i2s_state_ = I2S_STATE_STOPPED;
|
||||
|
||||
this->high_freq_.stop();
|
||||
this->state = media_player::MEDIA_PLAYER_STATE_IDLE;
|
||||
this->publish_state();
|
||||
this->is_announcement_ = false;
|
||||
}
|
||||
|
||||
media_player::MediaPlayerTraits I2SAudioMediaPlayer::get_traits() {
|
||||
auto traits = media_player::MediaPlayerTraits();
|
||||
traits.set_supports_pause(true);
|
||||
return traits;
|
||||
};
|
||||
|
||||
void I2SAudioMediaPlayer::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Audio:");
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGCONFIG(TAG, "Audio failed to initialize!");
|
||||
return;
|
||||
}
|
||||
#if SOC_I2S_SUPPORTS_DAC
|
||||
if (this->internal_dac_mode_ != I2S_DAC_CHANNEL_DISABLE) {
|
||||
switch (this->internal_dac_mode_) {
|
||||
case I2S_DAC_CHANNEL_LEFT_EN:
|
||||
ESP_LOGCONFIG(TAG, " Internal DAC mode: Left");
|
||||
break;
|
||||
case I2S_DAC_CHANNEL_RIGHT_EN:
|
||||
ESP_LOGCONFIG(TAG, " Internal DAC mode: Right");
|
||||
break;
|
||||
case I2S_DAC_CHANNEL_BOTH_EN:
|
||||
ESP_LOGCONFIG(TAG, " Internal DAC mode: Left & Right");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
#endif
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" External DAC channels: %d\n"
|
||||
" I2S DOUT Pin: %d",
|
||||
this->external_dac_channels_, this->dout_pin_);
|
||||
LOG_PIN(" Mute Pin: ", this->mute_pin_);
|
||||
#if SOC_I2S_SUPPORTS_DAC
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace i2s_audio
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32_FRAMEWORK_ARDUINO
|
||||
@@ -1,87 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
|
||||
|
||||
#include "../i2s_audio.h"
|
||||
|
||||
#include <driver/i2s.h>
|
||||
|
||||
#include "esphome/components/media_player/media_player.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/gpio.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <Audio.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace i2s_audio {
|
||||
|
||||
enum I2SState : uint8_t {
|
||||
I2S_STATE_STOPPED = 0,
|
||||
I2S_STATE_STARTING,
|
||||
I2S_STATE_RUNNING,
|
||||
I2S_STATE_STOPPING,
|
||||
};
|
||||
|
||||
class I2SAudioMediaPlayer : public Component, public Parented<I2SAudioComponent>, public media_player::MediaPlayer {
|
||||
public:
|
||||
void setup() override;
|
||||
float get_setup_priority() const override { return esphome::setup_priority::LATE; }
|
||||
|
||||
void loop() override;
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
void set_dout_pin(uint8_t pin) { this->dout_pin_ = pin; }
|
||||
void set_mute_pin(GPIOPin *mute_pin) { this->mute_pin_ = mute_pin; }
|
||||
#if SOC_I2S_SUPPORTS_DAC
|
||||
void set_internal_dac_mode(i2s_dac_mode_t mode) { this->internal_dac_mode_ = mode; }
|
||||
#endif
|
||||
void set_external_dac_channels(uint8_t channels) { this->external_dac_channels_ = channels; }
|
||||
|
||||
void set_i2s_comm_fmt_lsb(bool lsb) { this->i2s_comm_fmt_lsb_ = lsb; }
|
||||
|
||||
media_player::MediaPlayerTraits get_traits() override;
|
||||
|
||||
bool is_muted() const override { return this->muted_; }
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
protected:
|
||||
void control(const media_player::MediaPlayerCall &call) override;
|
||||
|
||||
void mute_();
|
||||
void unmute_();
|
||||
void set_volume_(float volume, bool publish = true);
|
||||
|
||||
void start_();
|
||||
void stop_();
|
||||
void play_();
|
||||
|
||||
I2SState i2s_state_{I2S_STATE_STOPPED};
|
||||
std::unique_ptr<Audio> audio_;
|
||||
|
||||
uint8_t dout_pin_{0};
|
||||
|
||||
GPIOPin *mute_pin_{nullptr};
|
||||
bool muted_{false};
|
||||
float unmuted_volume_{0};
|
||||
|
||||
#if SOC_I2S_SUPPORTS_DAC
|
||||
i2s_dac_mode_t internal_dac_mode_{I2S_DAC_CHANNEL_DISABLE};
|
||||
#endif
|
||||
uint8_t external_dac_channels_;
|
||||
|
||||
bool i2s_comm_fmt_lsb_;
|
||||
|
||||
HighFrequencyLoopRequester high_freq_;
|
||||
|
||||
optional<std::string> current_url_{};
|
||||
bool is_announcement_{false};
|
||||
};
|
||||
|
||||
} // namespace i2s_audio
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32_FRAMEWORK_ARDUINO
|
||||
@@ -1,14 +1,13 @@
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import audio, esp32, microphone
|
||||
from esphome.components.adc import ESP32_VARIANT_ADC1_PIN_TO_CHANNEL, validate_adc_pin
|
||||
from esphome.components.adc import validate_adc_pin
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BITS_PER_SAMPLE,
|
||||
CONF_CHANNEL,
|
||||
CONF_ID,
|
||||
CONF_NUM_CHANNELS,
|
||||
CONF_NUMBER,
|
||||
CONF_SAMPLE_RATE,
|
||||
)
|
||||
|
||||
@@ -23,7 +22,6 @@ from .. import (
|
||||
i2s_audio_component_schema,
|
||||
i2s_audio_ns,
|
||||
register_i2s_audio_component,
|
||||
use_legacy,
|
||||
validate_mclk_divisible_by_3,
|
||||
)
|
||||
|
||||
@@ -127,8 +125,10 @@ CONFIG_SCHEMA = cv.All(
|
||||
|
||||
|
||||
def _final_validate(config):
|
||||
if not use_legacy() and config[CONF_ADC_TYPE] == "internal":
|
||||
raise cv.Invalid("Internal ADC is only compatible with legacy i2s driver")
|
||||
if config[CONF_ADC_TYPE] == "internal":
|
||||
raise cv.Invalid(
|
||||
"Internal ADC is no longer supported. Use an external I2S microphone instead."
|
||||
)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
@@ -140,13 +140,7 @@ async def to_code(config):
|
||||
await register_i2s_audio_component(var, config)
|
||||
await microphone.register_microphone(var, config)
|
||||
|
||||
if config[CONF_ADC_TYPE] == "internal":
|
||||
variant = esp32.get_esp32_variant()
|
||||
pin_num = config[CONF_ADC_PIN][CONF_NUMBER]
|
||||
channel = ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant][pin_num]
|
||||
cg.add(var.set_adc_channel(channel))
|
||||
else:
|
||||
cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN]))
|
||||
cg.add(var.set_pdm(config[CONF_PDM]))
|
||||
cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN]))
|
||||
cg.add(var.set_pdm(config[CONF_PDM]))
|
||||
|
||||
cg.add(var.set_correct_dc_offset(config[CONF_CORRECT_DC_OFFSET]))
|
||||
|
||||
@@ -2,12 +2,8 @@
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#ifdef USE_I2S_LEGACY
|
||||
#include <driver/i2s.h>
|
||||
#else
|
||||
#include <driver/i2s_std.h>
|
||||
#include <driver/i2s_pdm.h>
|
||||
#endif
|
||||
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
@@ -65,13 +61,6 @@ void I2SAudioMicrophone::dump_config() {
|
||||
|
||||
void I2SAudioMicrophone::configure_stream_settings_() {
|
||||
uint8_t channel_count = 1;
|
||||
#ifdef USE_I2S_LEGACY
|
||||
uint8_t bits_per_sample = this->bits_per_sample_;
|
||||
|
||||
if (this->channel_ == I2S_CHANNEL_FMT_RIGHT_LEFT) {
|
||||
channel_count = 2;
|
||||
}
|
||||
#else
|
||||
uint8_t bits_per_sample = 16;
|
||||
if (this->slot_bit_width_ != I2S_SLOT_BIT_WIDTH_AUTO) {
|
||||
bits_per_sample = this->slot_bit_width_;
|
||||
@@ -80,7 +69,6 @@ void I2SAudioMicrophone::configure_stream_settings_() {
|
||||
if (this->slot_mode_ == I2S_SLOT_MODE_STEREO) {
|
||||
channel_count = 2;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
// ESP32 reads audio aligned to a multiple of 2 bytes. For example, if configured for 24 bits per sample, then it will
|
||||
@@ -114,65 +102,6 @@ bool I2SAudioMicrophone::start_driver_() {
|
||||
this->locked_driver_ = true;
|
||||
esp_err_t err;
|
||||
|
||||
#ifdef USE_I2S_LEGACY
|
||||
i2s_driver_config_t config = {
|
||||
.mode = (i2s_mode_t) (this->i2s_mode_ | I2S_MODE_RX),
|
||||
.sample_rate = this->sample_rate_,
|
||||
.bits_per_sample = this->bits_per_sample_,
|
||||
.channel_format = this->channel_,
|
||||
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
|
||||
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
|
||||
.dma_buf_count = 4,
|
||||
.dma_buf_len = 240, // Must be divisible by 3 to support 24 bits per sample on old driver and newer variants
|
||||
.use_apll = this->use_apll_,
|
||||
.tx_desc_auto_clear = false,
|
||||
.fixed_mclk = 0,
|
||||
.mclk_multiple = this->mclk_multiple_,
|
||||
.bits_per_chan = this->bits_per_channel_,
|
||||
};
|
||||
|
||||
#if SOC_I2S_SUPPORTS_ADC
|
||||
if (this->adc_) {
|
||||
config.mode = (i2s_mode_t) (config.mode | I2S_MODE_ADC_BUILT_IN);
|
||||
err = i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Error installing driver: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
err = i2s_set_adc_mode(ADC_UNIT_1, this->adc_channel_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Error setting ADC mode: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
err = i2s_adc_enable(this->parent_->get_port());
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Error enabling ADC: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
if (this->pdm_)
|
||||
config.mode = (i2s_mode_t) (config.mode | I2S_MODE_PDM);
|
||||
|
||||
err = i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Error installing driver: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
i2s_pin_config_t pin_config = this->parent_->get_pin_config();
|
||||
pin_config.data_in_num = this->din_pin_;
|
||||
|
||||
err = i2s_set_pin(this->parent_->get_port(), &pin_config);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Error setting pin: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#else
|
||||
i2s_chan_config_t chan_cfg = {
|
||||
.id = this->parent_->get_port(),
|
||||
.role = this->i2s_role_,
|
||||
@@ -265,7 +194,6 @@ bool I2SAudioMicrophone::start_driver_() {
|
||||
ESP_LOGE(TAG, "Enabling failed: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
this->configure_stream_settings_(); // redetermine the settings in case some settings were changed after compilation
|
||||
|
||||
@@ -284,24 +212,6 @@ void I2SAudioMicrophone::stop_driver_() {
|
||||
// ensures that we stop/unload the driver when it only partially starts.
|
||||
|
||||
esp_err_t err;
|
||||
#ifdef USE_I2S_LEGACY
|
||||
#if SOC_I2S_SUPPORTS_ADC
|
||||
if (this->adc_) {
|
||||
err = i2s_adc_disable(this->parent_->get_port());
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Error disabling ADC: %s", esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
err = i2s_stop(this->parent_->get_port());
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Error stopping: %s", esp_err_to_name(err));
|
||||
}
|
||||
err = i2s_driver_uninstall(this->parent_->get_port());
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Error uninstalling driver: %s", esp_err_to_name(err));
|
||||
}
|
||||
#else
|
||||
if (this->rx_handle_ != nullptr) {
|
||||
/* Have to stop the channel before deleting it */
|
||||
err = i2s_channel_disable(this->rx_handle_);
|
||||
@@ -315,7 +225,6 @@ void I2SAudioMicrophone::stop_driver_() {
|
||||
}
|
||||
this->rx_handle_ = nullptr;
|
||||
}
|
||||
#endif
|
||||
if (this->locked_driver_) {
|
||||
this->parent_->unlock();
|
||||
this->locked_driver_ = false;
|
||||
@@ -412,12 +321,8 @@ void I2SAudioMicrophone::fix_dc_offset_(std::vector<uint8_t> &data) {
|
||||
|
||||
size_t I2SAudioMicrophone::read_(uint8_t *buf, size_t len, TickType_t ticks_to_wait) {
|
||||
size_t bytes_read = 0;
|
||||
#ifdef USE_I2S_LEGACY
|
||||
esp_err_t err = i2s_read(this->parent_->get_port(), buf, len, &bytes_read, ticks_to_wait);
|
||||
#else
|
||||
// i2s_channel_read expects the timeout value in ms, not ticks
|
||||
esp_err_t err = i2s_channel_read(this->rx_handle_, buf, len, &bytes_read, pdTICKS_TO_MS(ticks_to_wait));
|
||||
#endif
|
||||
if ((err != ESP_OK) && ((err != ESP_ERR_TIMEOUT) || (ticks_to_wait != 0))) {
|
||||
// Ignore ESP_ERR_TIMEOUT if ticks_to_wait = 0, as it will read the data on the next call
|
||||
if (!this->status_has_warning()) {
|
||||
@@ -432,7 +337,7 @@ size_t I2SAudioMicrophone::read_(uint8_t *buf, size_t len, TickType_t ticks_to_w
|
||||
return 0;
|
||||
}
|
||||
this->status_clear_warning();
|
||||
#if defined(USE_ESP32_VARIANT_ESP32) and not defined(USE_I2S_LEGACY)
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
// For ESP32 16-bit standard mono mode, adjacent samples need to be swapped.
|
||||
if (this->slot_mode_ == I2S_SLOT_MODE_MONO && this->slot_bit_width_ == I2S_SLOT_BIT_WIDTH_16BIT && !this->pdm_) {
|
||||
int16_t *samples = reinterpret_cast<int16_t *>(buf);
|
||||
|
||||
@@ -26,23 +26,10 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
|
||||
|
||||
void set_correct_dc_offset(bool correct_dc_offset) { this->correct_dc_offset_ = correct_dc_offset; }
|
||||
|
||||
#ifdef USE_I2S_LEGACY
|
||||
void set_din_pin(int8_t pin) { this->din_pin_ = pin; }
|
||||
#else
|
||||
void set_din_pin(int8_t pin) { this->din_pin_ = (gpio_num_t) pin; }
|
||||
#endif
|
||||
|
||||
void set_pdm(bool pdm) { this->pdm_ = pdm; }
|
||||
|
||||
#ifdef USE_I2S_LEGACY
|
||||
#if SOC_I2S_SUPPORTS_ADC
|
||||
void set_adc_channel(adc_channel_t channel) {
|
||||
this->adc_channel_ = (adc1_channel_t) channel;
|
||||
this->adc_ = true;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
protected:
|
||||
/// @brief Starts the I2S driver. Updates the ``audio_stream_info_`` member variable with the current setttings.
|
||||
/// @return True if succesful, false otherwise
|
||||
@@ -68,16 +55,8 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
|
||||
|
||||
TaskHandle_t task_handle_{nullptr};
|
||||
|
||||
#ifdef USE_I2S_LEGACY
|
||||
int8_t din_pin_{I2S_PIN_NO_CHANGE};
|
||||
#if SOC_I2S_SUPPORTS_ADC
|
||||
adc1_channel_t adc_channel_{ADC1_CHANNEL_MAX};
|
||||
bool adc_{false};
|
||||
#endif
|
||||
#else
|
||||
gpio_num_t din_pin_{I2S_GPIO_UNUSED};
|
||||
i2s_chan_handle_t rx_handle_;
|
||||
#endif
|
||||
bool pdm_{false};
|
||||
|
||||
bool correct_dc_offset_;
|
||||
|
||||
@@ -26,7 +26,6 @@ from .. import (
|
||||
i2s_audio_component_schema,
|
||||
i2s_audio_ns,
|
||||
register_i2s_audio_component,
|
||||
use_legacy,
|
||||
validate_mclk_divisible_by_3,
|
||||
)
|
||||
|
||||
@@ -166,13 +165,12 @@ CONFIG_SCHEMA = cv.All(
|
||||
|
||||
|
||||
def _final_validate(config):
|
||||
if not use_legacy():
|
||||
if config[CONF_DAC_TYPE] == "internal":
|
||||
raise cv.Invalid("Internal DAC is only compatible with legacy i2s driver")
|
||||
if config[CONF_I2S_COMM_FMT] == "stand_max":
|
||||
raise cv.Invalid(
|
||||
"I2S standard max format only implemented with legacy i2s driver."
|
||||
)
|
||||
if config[CONF_DAC_TYPE] == "internal":
|
||||
raise cv.Invalid(
|
||||
"Internal DAC is no longer supported. Use an external I2S DAC instead."
|
||||
)
|
||||
if config[CONF_I2S_COMM_FMT] == "stand_max":
|
||||
raise cv.Invalid("I2S standard max format is no longer supported.")
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
@@ -184,21 +182,13 @@ async def to_code(config):
|
||||
await register_i2s_audio_component(var, config)
|
||||
await speaker.register_speaker(var, config)
|
||||
|
||||
if config[CONF_DAC_TYPE] == "internal":
|
||||
cg.add(var.set_internal_dac_mode(config[CONF_MODE]))
|
||||
else:
|
||||
cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN]))
|
||||
if use_legacy():
|
||||
cg.add(
|
||||
var.set_i2s_comm_fmt(I2C_COMM_FMT_OPTIONS[config[CONF_I2S_COMM_FMT]])
|
||||
)
|
||||
else:
|
||||
fmt = "std" # equals stand_i2s, stand_pcm_long, i2s_msb, pcm_long
|
||||
if config[CONF_I2S_COMM_FMT] in ["stand_msb", "i2s_lsb"]:
|
||||
fmt = "msb"
|
||||
elif config[CONF_I2S_COMM_FMT] in ["stand_pcm_short", "pcm_short", "pcm"]:
|
||||
fmt = "pcm"
|
||||
cg.add(var.set_i2s_comm_fmt(fmt))
|
||||
cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN]))
|
||||
fmt = "std" # equals stand_i2s, stand_pcm_long, i2s_msb, pcm_long
|
||||
if config[CONF_I2S_COMM_FMT] in ["stand_msb", "i2s_lsb"]:
|
||||
fmt = "msb"
|
||||
elif config[CONF_I2S_COMM_FMT] in ["stand_pcm_short", "pcm_short", "pcm"]:
|
||||
fmt = "pcm"
|
||||
cg.add(var.set_i2s_comm_fmt(fmt))
|
||||
if config[CONF_TIMEOUT] != CONF_NEVER:
|
||||
cg.add(var.set_timeout(config[CONF_TIMEOUT]))
|
||||
cg.add(var.set_buffer_duration(config[CONF_BUFFER_DURATION]))
|
||||
|
||||
@@ -2,11 +2,7 @@
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#ifdef USE_I2S_LEGACY
|
||||
#include <driver/i2s.h>
|
||||
#else
|
||||
#include <driver/i2s_std.h>
|
||||
#endif
|
||||
|
||||
#include "esphome/components/audio/audio.h"
|
||||
#include "esphome/components/audio/audio_transfer_buffer.h"
|
||||
@@ -79,14 +75,7 @@ void I2SAudioSpeaker::dump_config() {
|
||||
if (this->timeout_.has_value()) {
|
||||
ESP_LOGCONFIG(TAG, " Timeout: %" PRIu32 " ms", this->timeout_.value());
|
||||
}
|
||||
#ifdef USE_I2S_LEGACY
|
||||
#if SOC_I2S_SUPPORTS_DAC
|
||||
ESP_LOGCONFIG(TAG, " Internal DAC mode: %d", static_cast<int8_t>(this->internal_dac_mode_));
|
||||
#endif
|
||||
ESP_LOGCONFIG(TAG, " Communication format: %d", static_cast<int8_t>(this->i2s_comm_fmt_));
|
||||
#else
|
||||
ESP_LOGCONFIG(TAG, " Communication format: %s", this->i2s_comm_fmt_.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
void I2SAudioSpeaker::loop() {
|
||||
@@ -300,14 +289,6 @@ void I2SAudioSpeaker::speaker_task(void *params) {
|
||||
// Audio stream info changed, stop the speaker task so it will restart with the proper settings.
|
||||
break;
|
||||
}
|
||||
#ifdef USE_I2S_LEGACY
|
||||
i2s_event_t i2s_event;
|
||||
while (xQueueReceive(this_speaker->i2s_event_queue_, &i2s_event, 0)) {
|
||||
if (i2s_event.type == I2S_EVENT_TX_Q_OVF) {
|
||||
tx_dma_underflow = true;
|
||||
}
|
||||
}
|
||||
#else
|
||||
int64_t write_timestamp;
|
||||
while (xQueueReceive(this_speaker->i2s_event_queue_, &write_timestamp, 0)) {
|
||||
// Receives timing events from the I2S on_sent callback. If actual audio data was sent in this event, it passes
|
||||
@@ -326,7 +307,6 @@ void I2SAudioSpeaker::speaker_task(void *params) {
|
||||
this_speaker->audio_output_callback_(frames_sent, write_timestamp);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (this_speaker->pause_state_) {
|
||||
// Pause state is accessed atomically, so thread safe
|
||||
@@ -393,17 +373,6 @@ void I2SAudioSpeaker::speaker_task(void *params) {
|
||||
vTaskDelay(pdMS_TO_TICKS(DMA_BUFFER_DURATION_MS / 2));
|
||||
} else {
|
||||
size_t bytes_written = 0;
|
||||
#ifdef USE_I2S_LEGACY
|
||||
if (this_speaker->current_stream_info_.get_bits_per_sample() == (uint8_t) this_speaker->bits_per_sample_) {
|
||||
i2s_write(this_speaker->parent_->get_port(), transfer_buffer->get_buffer_start(),
|
||||
transfer_buffer->available(), &bytes_written, pdMS_TO_TICKS(DMA_BUFFER_DURATION_MS));
|
||||
} else if (this_speaker->current_stream_info_.get_bits_per_sample() <
|
||||
(uint8_t) this_speaker->bits_per_sample_) {
|
||||
i2s_write_expand(this_speaker->parent_->get_port(), transfer_buffer->get_buffer_start(),
|
||||
transfer_buffer->available(), this_speaker->current_stream_info_.get_bits_per_sample(),
|
||||
this_speaker->bits_per_sample_, &bytes_written, pdMS_TO_TICKS(DMA_BUFFER_DURATION_MS));
|
||||
}
|
||||
#else
|
||||
if (tx_dma_underflow) {
|
||||
// Temporarily disable channel and callback to reset the I2S driver's internal DMA buffer queue so timing
|
||||
// callbacks are accurate. Preload the data.
|
||||
@@ -420,14 +389,12 @@ void I2SAudioSpeaker::speaker_task(void *params) {
|
||||
i2s_channel_write(this_speaker->tx_handle_, transfer_buffer->get_buffer_start(), transfer_buffer->available(),
|
||||
&bytes_written, DMA_BUFFER_DURATION_MS);
|
||||
}
|
||||
#endif
|
||||
if (bytes_written > 0) {
|
||||
last_data_received_time = millis();
|
||||
frames_written += this_speaker->current_stream_info_.bytes_to_frames(bytes_written);
|
||||
transfer_buffer->decrease_buffer_length(bytes_written);
|
||||
if (tx_dma_underflow) {
|
||||
tx_dma_underflow = false;
|
||||
#ifndef USE_I2S_LEGACY
|
||||
// Reset the event queue timestamps
|
||||
// Enable the on_sent callback to accurately track the timestamps of played audio
|
||||
// Enable the I2S channel to start sending the preloaded audio
|
||||
@@ -440,14 +407,7 @@ void I2SAudioSpeaker::speaker_task(void *params) {
|
||||
i2s_channel_register_event_callback(this_speaker->tx_handle_, &callbacks, this_speaker);
|
||||
|
||||
i2s_channel_enable(this_speaker->tx_handle_);
|
||||
#endif
|
||||
}
|
||||
#ifdef USE_I2S_LEGACY
|
||||
// The legacy driver doesn't easily support the callback approach for timestamps, so fall back to a direct but
|
||||
// less accurate approach.
|
||||
this_speaker->audio_output_callback_(this_speaker->current_stream_info_.bytes_to_frames(bytes_written),
|
||||
esp_timer_get_time() + dma_buffers_duration_ms * 1000);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -496,22 +456,14 @@ void I2SAudioSpeaker::stop_(bool wait_on_empty) {
|
||||
esp_err_t I2SAudioSpeaker::start_i2s_driver_(audio::AudioStreamInfo &audio_stream_info) {
|
||||
this->current_stream_info_ = audio_stream_info; // store the stream info settings the driver will use
|
||||
|
||||
#ifdef USE_I2S_LEGACY
|
||||
if ((this->i2s_mode_ & I2S_MODE_SLAVE) && (this->sample_rate_ != audio_stream_info.get_sample_rate())) { // NOLINT
|
||||
#else
|
||||
if ((this->i2s_role_ & I2S_ROLE_SLAVE) && (this->sample_rate_ != audio_stream_info.get_sample_rate())) { // NOLINT
|
||||
#endif
|
||||
// Can't reconfigure I2S bus, so the sample rate must match the configured value
|
||||
ESP_LOGE(TAG, "Audio stream settings are not compatible with this I2S configuration");
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
#ifdef USE_I2S_LEGACY
|
||||
if ((i2s_bits_per_sample_t) audio_stream_info.get_bits_per_sample() > this->bits_per_sample_) {
|
||||
#else
|
||||
if (this->slot_bit_width_ != I2S_SLOT_BIT_WIDTH_AUTO &&
|
||||
(i2s_slot_bit_width_t) audio_stream_info.get_bits_per_sample() > this->slot_bit_width_) {
|
||||
#endif
|
||||
// Currently can't handle the case when the incoming audio has more bits per sample than the configured value
|
||||
ESP_LOGE(TAG, "Audio streams with more bits per sample than the I2S speaker's configuration is not supported");
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
@@ -524,77 +476,6 @@ esp_err_t I2SAudioSpeaker::start_i2s_driver_(audio::AudioStreamInfo &audio_strea
|
||||
|
||||
uint32_t dma_buffer_length = audio_stream_info.ms_to_frames(DMA_BUFFER_DURATION_MS);
|
||||
|
||||
#ifdef USE_I2S_LEGACY
|
||||
i2s_channel_fmt_t channel = this->channel_;
|
||||
|
||||
if (audio_stream_info.get_channels() == 1) {
|
||||
if (this->channel_ == I2S_CHANNEL_FMT_ONLY_LEFT) {
|
||||
channel = I2S_CHANNEL_FMT_ONLY_LEFT;
|
||||
} else {
|
||||
channel = I2S_CHANNEL_FMT_ONLY_RIGHT;
|
||||
}
|
||||
} else if (audio_stream_info.get_channels() == 2) {
|
||||
channel = I2S_CHANNEL_FMT_RIGHT_LEFT;
|
||||
}
|
||||
|
||||
i2s_driver_config_t config = {
|
||||
.mode = (i2s_mode_t) (this->i2s_mode_ | I2S_MODE_TX),
|
||||
.sample_rate = audio_stream_info.get_sample_rate(),
|
||||
.bits_per_sample = this->bits_per_sample_,
|
||||
.channel_format = channel,
|
||||
.communication_format = this->i2s_comm_fmt_,
|
||||
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
|
||||
.dma_buf_count = DMA_BUFFERS_COUNT,
|
||||
.dma_buf_len = (int) dma_buffer_length,
|
||||
.use_apll = this->use_apll_,
|
||||
.tx_desc_auto_clear = true,
|
||||
.fixed_mclk = I2S_PIN_NO_CHANGE,
|
||||
.mclk_multiple = this->mclk_multiple_,
|
||||
.bits_per_chan = this->bits_per_channel_,
|
||||
#if SOC_I2S_SUPPORTS_TDM
|
||||
.chan_mask = (i2s_channel_t) (I2S_TDM_ACTIVE_CH0 | I2S_TDM_ACTIVE_CH1),
|
||||
.total_chan = 2,
|
||||
.left_align = false,
|
||||
.big_edin = false,
|
||||
.bit_order_msb = false,
|
||||
.skip_msk = false,
|
||||
#endif
|
||||
};
|
||||
#if SOC_I2S_SUPPORTS_DAC
|
||||
if (this->internal_dac_mode_ != I2S_DAC_CHANNEL_DISABLE) {
|
||||
config.mode = (i2s_mode_t) (config.mode | I2S_MODE_DAC_BUILT_IN);
|
||||
}
|
||||
#endif
|
||||
|
||||
esp_err_t err =
|
||||
i2s_driver_install(this->parent_->get_port(), &config, I2S_EVENT_QUEUE_COUNT, &this->i2s_event_queue_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to install I2S legacy driver");
|
||||
// Failed to install the driver, so unlock the I2S port
|
||||
this->parent_->unlock();
|
||||
return err;
|
||||
}
|
||||
|
||||
#if SOC_I2S_SUPPORTS_DAC
|
||||
if (this->internal_dac_mode_ == I2S_DAC_CHANNEL_DISABLE) {
|
||||
#endif
|
||||
i2s_pin_config_t pin_config = this->parent_->get_pin_config();
|
||||
pin_config.data_out_num = this->dout_pin_;
|
||||
|
||||
err = i2s_set_pin(this->parent_->get_port(), &pin_config);
|
||||
#if SOC_I2S_SUPPORTS_DAC
|
||||
} else {
|
||||
i2s_set_dac_mode(this->internal_dac_mode_);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (err != ESP_OK) {
|
||||
// Failed to set the data out pin, so uninstall the driver and unlock the I2S port
|
||||
ESP_LOGE(TAG, "Failed to set the data out pin");
|
||||
i2s_driver_uninstall(this->parent_->get_port());
|
||||
this->parent_->unlock();
|
||||
}
|
||||
#else
|
||||
i2s_chan_config_t chan_cfg = {
|
||||
.id = this->parent_->get_port(),
|
||||
.role = this->i2s_role_,
|
||||
@@ -683,12 +564,10 @@ esp_err_t I2SAudioSpeaker::start_i2s_driver_(audio::AudioStreamInfo &audio_strea
|
||||
}
|
||||
|
||||
i2s_channel_enable(this->tx_handle_);
|
||||
#endif
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
#ifndef USE_I2S_LEGACY
|
||||
bool IRAM_ATTR I2SAudioSpeaker::i2s_on_sent_cb(i2s_chan_handle_t handle, i2s_event_data_t *event, void *user_ctx) {
|
||||
int64_t now = esp_timer_get_time();
|
||||
|
||||
@@ -709,16 +588,11 @@ bool IRAM_ATTR I2SAudioSpeaker::i2s_on_sent_cb(i2s_chan_handle_t handle, i2s_eve
|
||||
|
||||
return need_yield1 | need_yield2 | need_yield3;
|
||||
}
|
||||
#endif
|
||||
|
||||
void I2SAudioSpeaker::stop_i2s_driver_() {
|
||||
#ifdef USE_I2S_LEGACY
|
||||
i2s_driver_uninstall(this->parent_->get_port());
|
||||
#else
|
||||
i2s_channel_disable(this->tx_handle_);
|
||||
i2s_del_channel(this->tx_handle_);
|
||||
this->tx_handle_ = nullptr;
|
||||
#endif
|
||||
this->parent_->unlock();
|
||||
}
|
||||
|
||||
|
||||
@@ -29,16 +29,8 @@ class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Comp
|
||||
|
||||
void set_buffer_duration(uint32_t buffer_duration_ms) { this->buffer_duration_ms_ = buffer_duration_ms; }
|
||||
void set_timeout(uint32_t ms) { this->timeout_ = ms; }
|
||||
#ifdef USE_I2S_LEGACY
|
||||
#if SOC_I2S_SUPPORTS_DAC
|
||||
void set_internal_dac_mode(i2s_dac_mode_t mode) { this->internal_dac_mode_ = mode; }
|
||||
#endif
|
||||
void set_dout_pin(uint8_t pin) { this->dout_pin_ = pin; }
|
||||
void set_i2s_comm_fmt(i2s_comm_format_t mode) { this->i2s_comm_fmt_ = mode; }
|
||||
#else
|
||||
void set_dout_pin(uint8_t pin) { this->dout_pin_ = (gpio_num_t) pin; }
|
||||
void set_i2s_comm_fmt(std::string mode) { this->i2s_comm_fmt_ = std::move(mode); }
|
||||
#endif
|
||||
|
||||
void start() override;
|
||||
void stop() override;
|
||||
@@ -83,14 +75,12 @@ class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Comp
|
||||
/// @param wait_on_empty If false, sends the COMMAND_STOP signal. If true, sends the COMMAND_STOP_GRACEFULLY signal.
|
||||
void stop_(bool wait_on_empty);
|
||||
|
||||
#ifndef USE_I2S_LEGACY
|
||||
/// @brief Callback function used to send playback timestamps the to the speaker task.
|
||||
/// @param handle (i2s_chan_handle_t)
|
||||
/// @param event (i2s_event_data_t)
|
||||
/// @param user_ctx (void*) User context pointer that the callback accesses
|
||||
/// @return True if a higher priority task was interrupted
|
||||
static bool i2s_on_sent_cb(i2s_chan_handle_t handle, i2s_event_data_t *event, void *user_ctx);
|
||||
#endif
|
||||
|
||||
/// @brief Starts the ESP32 I2S driver.
|
||||
/// Attempts to lock the I2S port, starts the I2S driver using the passed in stream information, and sets the data out
|
||||
@@ -124,17 +114,9 @@ class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Comp
|
||||
|
||||
audio::AudioStreamInfo current_stream_info_; // The currently loaded driver's stream info
|
||||
|
||||
#ifdef USE_I2S_LEGACY
|
||||
#if SOC_I2S_SUPPORTS_DAC
|
||||
i2s_dac_mode_t internal_dac_mode_{I2S_DAC_CHANNEL_DISABLE};
|
||||
#endif
|
||||
uint8_t dout_pin_;
|
||||
i2s_comm_format_t i2s_comm_fmt_;
|
||||
#else
|
||||
gpio_num_t dout_pin_;
|
||||
std::string i2s_comm_fmt_;
|
||||
i2s_chan_handle_t tx_handle_;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace i2s_audio
|
||||
|
||||
@@ -185,7 +185,6 @@
|
||||
#ifdef USE_ARDUINO
|
||||
#define USE_PROMETHEUS
|
||||
#define USE_WIFI_WPA2_EAP
|
||||
#define USE_I2S_LEGACY
|
||||
#endif
|
||||
|
||||
// Platforms with native 64-bit time sources (no rollover tracking needed)
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
substitutions:
|
||||
i2s_bclk_pin: GPIO15
|
||||
i2s_lrclk_pin: GPIO4
|
||||
i2s_mclk_pin: GPIO5
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
wifi:
|
||||
ssid: test
|
||||
password: test1234
|
||||
|
||||
media_player:
|
||||
- platform: i2s_audio
|
||||
name: Test Media Player
|
||||
dac_type: internal
|
||||
mode: stereo
|
||||
@@ -1,18 +1,18 @@
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
i2s_audio:
|
||||
i2s_lrclk_pin: 13
|
||||
i2s_bclk_pin: 14
|
||||
i2s_mclk_pin: 15
|
||||
|
||||
media_player:
|
||||
speaker:
|
||||
- platform: i2s_audio
|
||||
name: None
|
||||
dac_type: external
|
||||
id: test_speaker
|
||||
i2s_dout_pin: 18
|
||||
mute_pin: 19
|
||||
dac_type: external
|
||||
|
||||
media_player:
|
||||
- platform: speaker
|
||||
name: None
|
||||
speaker: test_speaker
|
||||
on_state:
|
||||
- media_player.play:
|
||||
- media_player.play_media: http://localhost/media.mp3
|
||||
|
||||
Reference in New Issue
Block a user