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:
@@ -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)
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user