1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-28 05:33:53 +00:00

[speaker, i2s_audio] I2S Speaker implementation using a ring buffer (#7605)

This commit is contained in:
Kevin Ahrendt
2024-10-16 18:47:11 -04:00
committed by GitHub
parent 22478ffb0f
commit 1c845e0ff8
13 changed files with 601 additions and 266 deletions

View File

@@ -2,7 +2,7 @@ from esphome import automation
from esphome.automation import maybe_simple_id
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_DATA, CONF_ID
from esphome.const import CONF_DATA, CONF_ID, CONF_VOLUME
from esphome.core import CORE
from esphome.coroutine import coroutine_with_priority
@@ -23,6 +23,10 @@ StopAction = speaker_ns.class_(
FinishAction = speaker_ns.class_(
"FinishAction", automation.Action, cg.Parented.template(Speaker)
)
VolumeSetAction = speaker_ns.class_(
"VolumeSetAction", automation.Action, cg.Parented.template(Speaker)
)
IsPlayingCondition = speaker_ns.class_("IsPlayingCondition", automation.Condition)
IsStoppedCondition = speaker_ns.class_("IsStoppedCondition", automation.Condition)
@@ -90,6 +94,25 @@ automation.register_condition(
)(speaker_action)
@automation.register_action(
"speaker.volume_set",
VolumeSetAction,
cv.maybe_simple_value(
{
cv.GenerateID(): cv.use_id(Speaker),
cv.Required(CONF_VOLUME): cv.templatable(cv.percentage),
},
key=CONF_VOLUME,
),
)
async def speaker_volume_set_action(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
volume = await cg.templatable(config[CONF_VOLUME], args, float)
cg.add(var.set_volume(volume))
return var
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(speaker_ns.using)

View File

@@ -34,6 +34,11 @@ template<typename... Ts> class PlayAction : public Action<Ts...>, public Parente
std::vector<uint8_t> data_static_{};
};
template<typename... Ts> class VolumeSetAction : public Action<Ts...>, public Parented<Speaker> {
TEMPLATABLE_VALUE(float, volume)
void play(Ts... x) override { this->parent_->set_volume(this->volume_.value(x...)); }
};
template<typename... Ts> class StopAction : public Action<Ts...>, public Parented<Speaker> {
public:
void play(Ts... x) override { this->parent_->stop(); }

View File

@@ -4,6 +4,12 @@
#include <cstdint>
#include <vector>
#ifdef USE_ESP32
#include <freertos/FreeRTOS.h>
#endif
#include "esphome/components/audio/audio.h"
namespace esphome {
namespace speaker {
@@ -16,14 +22,33 @@ enum State : uint8_t {
class Speaker {
public:
#ifdef USE_ESP32
/// @brief Plays the provided audio data.
/// If the speaker component doesn't implement this method, it falls back to the play method without this parameter.
/// @param data Audio data in the format specified by ``set_audio_stream_info`` method.
/// @param length The length of the audio data in bytes.
/// @param ticks_to_wait The FreeRTOS ticks to wait before writing as much data as possible to the ring buffer.
/// @return The number of bytes that were actually written to the speaker's internal buffer.
virtual size_t play(const uint8_t *data, size_t length, TickType_t ticks_to_wait) {
return this->play(data, length);
};
#endif
/// @brief Plays the provided audio data.
/// If the audio stream is not the default defined in "esphome/core/audio.h" and the speaker component implements it,
/// then this should be called after calling ``set_audio_stream_info``.
/// @param data Audio data in the format specified by ``set_audio_stream_info`` method.
/// @param length The length of the audio data in bytes.
/// @return The number of bytes that were actually written to the speaker's internal buffer.
virtual size_t play(const uint8_t *data, size_t length) = 0;
size_t play(const std::vector<uint8_t> &data) { return this->play(data.data(), data.size()); }
virtual void start() = 0;
virtual void stop() = 0;
// In compare between *STOP()* and *FINISH()*; *FINISH()* will stop after emptying the play buffer,
// while *STOP()* will break directly.
// When finish() is not implemented on the plateform component it should just do a normal stop.
// When finish() is not implemented on the platform component it should just do a normal stop.
virtual void finish() { this->stop(); }
virtual bool has_buffered_data() const = 0;
@@ -31,8 +56,18 @@ class Speaker {
bool is_running() const { return this->state_ == STATE_RUNNING; }
bool is_stopped() const { return this->state_ == STATE_STOPPED; }
// Volume control must be implemented by each speaker component, otherwise it will have no effect.
virtual void set_volume(float volume) { this->volume_ = volume; };
virtual float get_volume() { return this->volume_; }
void set_audio_stream_info(const audio::AudioStreamInfo &audio_stream_info) {
this->audio_stream_info_ = audio_stream_info;
}
protected:
State state_{STATE_STOPPED};
audio::AudioStreamInfo audio_stream_info_;
float volume_{1.0f};
};
} // namespace speaker