mirror of
https://github.com/esphome/esphome.git
synced 2025-04-15 15:20:27 +01:00
160 lines
4.9 KiB
C++
160 lines
4.9 KiB
C++
#pragma once
|
|
|
|
#ifdef USE_ESP_IDF
|
|
|
|
#include "esphome/components/audio/audio.h"
|
|
#include "esphome/components/audio/audio_reader.h"
|
|
#include "esphome/components/audio/audio_decoder.h"
|
|
#include "esphome/components/speaker/speaker.h"
|
|
|
|
#include "esphome/core/ring_buffer.h"
|
|
|
|
#include "esp_err.h"
|
|
|
|
#include <freertos/FreeRTOS.h>
|
|
#include <freertos/event_groups.h>
|
|
#include <freertos/queue.h>
|
|
|
|
namespace esphome {
|
|
namespace speaker {
|
|
|
|
// Internal sink/source buffers for reader and decoder
|
|
static const size_t DEFAULT_TRANSFER_BUFFER_SIZE = 24 * 1024;
|
|
|
|
enum class AudioPipelineType : uint8_t {
|
|
MEDIA,
|
|
ANNOUNCEMENT,
|
|
};
|
|
|
|
enum class AudioPipelineState : uint8_t {
|
|
STARTING_FILE,
|
|
STARTING_URL,
|
|
PLAYING,
|
|
STOPPING,
|
|
STOPPED,
|
|
PAUSED,
|
|
ERROR_READING,
|
|
ERROR_DECODING,
|
|
};
|
|
|
|
enum class InfoErrorSource : uint8_t {
|
|
READER = 0,
|
|
DECODER,
|
|
};
|
|
|
|
enum class DecodingError : uint8_t {
|
|
FAILED_HEADER = 0,
|
|
INCOMPATIBLE_BITS_PER_SAMPLE,
|
|
INCOMPATIBLE_CHANNELS,
|
|
};
|
|
|
|
// Used to pass information from each task.
|
|
struct InfoErrorEvent {
|
|
InfoErrorSource source;
|
|
optional<esp_err_t> err;
|
|
optional<audio::AudioFileType> file_type;
|
|
optional<audio::AudioStreamInfo> audio_stream_info;
|
|
optional<DecodingError> decoding_err;
|
|
};
|
|
|
|
class AudioPipeline {
|
|
public:
|
|
/// @param speaker ESPHome speaker component for pipeline's audio output
|
|
/// @param buffer_size Size of the buffer in bytes between the reader and decoder
|
|
/// @param task_stack_in_psram True if the task stack should be allocated in PSRAM, false otherwise
|
|
/// @param task_name FreeRTOS task base name
|
|
/// @param priority FreeRTOS task priority
|
|
AudioPipeline(speaker::Speaker *speaker, size_t buffer_size, bool task_stack_in_psram, std::string base_name,
|
|
UBaseType_t priority);
|
|
|
|
/// @brief Starts an audio pipeline given a media url
|
|
/// @param uri media file url
|
|
/// @return ESP_OK if successful or an appropriate error if not
|
|
void start_url(const std::string &uri);
|
|
|
|
/// @brief Starts an audio pipeline given a AudioFile pointer
|
|
/// @param audio_file pointer to an AudioFile object
|
|
/// @return ESP_OK if successful or an appropriate error if not
|
|
void start_file(audio::AudioFile *audio_file);
|
|
|
|
/// @brief Stops the pipeline. Sends a stop signal to each task (if running) and clears the ring buffers.
|
|
/// @return ESP_OK if successful or ESP_ERR_TIMEOUT if the tasks did not indicate they stopped
|
|
esp_err_t stop();
|
|
|
|
/// @brief Processes the state of the audio pipeline based on the info_error_queue_ and event_group_. Handles creating
|
|
/// and stopping the pipeline tasks. Needs to be regularly called to update the internal pipeline state.
|
|
/// @return AudioPipelineState
|
|
AudioPipelineState process_state();
|
|
|
|
/// @brief Suspends any running tasks
|
|
void suspend_tasks();
|
|
/// @brief Resumes any running tasks
|
|
void resume_tasks();
|
|
|
|
uint32_t get_playback_ms() { return this->playback_ms_; }
|
|
|
|
void set_pause_state(bool pause_state);
|
|
|
|
protected:
|
|
/// @brief Allocates the event group and info error queue.
|
|
/// @return ESP_OK if successful or ESP_ERR_NO_MEM if it is unable to allocate all parts
|
|
esp_err_t allocate_communications_();
|
|
|
|
/// @brief Common start code for the pipeline, regardless if the source is a file or url.
|
|
/// @return ESP_OK if successful or an appropriate error if not
|
|
esp_err_t start_tasks_();
|
|
|
|
/// @brief Resets the task related pointers and deallocates their stacks.
|
|
void delete_tasks_();
|
|
|
|
std::string base_name_;
|
|
UBaseType_t priority_;
|
|
|
|
uint32_t playback_ms_{0};
|
|
|
|
bool hard_stop_{false};
|
|
bool is_playing_{false};
|
|
bool pause_state_{false};
|
|
bool task_stack_in_psram_;
|
|
|
|
// Pending file start state used to ensure the pipeline fully stops before attempting to start the next file
|
|
bool pending_url_{false};
|
|
bool pending_file_{false};
|
|
|
|
speaker::Speaker *speaker_{nullptr};
|
|
|
|
std::string current_uri_{};
|
|
audio::AudioFile *current_audio_file_{nullptr};
|
|
|
|
audio::AudioFileType current_audio_file_type_;
|
|
audio::AudioStreamInfo current_audio_stream_info_;
|
|
|
|
size_t buffer_size_; // Ring buffer between reader and decoder
|
|
size_t transfer_buffer_size_; // Internal source/sink buffers for the audio reader and decoder
|
|
|
|
std::weak_ptr<RingBuffer> raw_file_ring_buffer_;
|
|
|
|
// Handles basic control/state of the three tasks
|
|
EventGroupHandle_t event_group_{nullptr};
|
|
|
|
// Receives detailed info (file type, stream info, resampling info) or specific errors from the three tasks
|
|
QueueHandle_t info_error_queue_{nullptr};
|
|
|
|
// Handles reading the media file from flash or a url
|
|
static void read_task(void *params);
|
|
TaskHandle_t read_task_handle_{nullptr};
|
|
StaticTask_t read_task_stack_;
|
|
StackType_t *read_task_stack_buffer_{nullptr};
|
|
|
|
// Decodes the media file into PCM audio
|
|
static void decode_task(void *params);
|
|
TaskHandle_t decode_task_handle_{nullptr};
|
|
StaticTask_t decode_task_stack_;
|
|
StackType_t *decode_task_stack_buffer_{nullptr};
|
|
};
|
|
|
|
} // namespace speaker
|
|
} // namespace esphome
|
|
|
|
#endif
|