mirror of
https://github.com/esphome/esphome.git
synced 2025-02-08 06:00:56 +00:00
[audio] Media Player Components PR3 (#8165)
This commit is contained in:
parent
f6cf99384b
commit
6e5e681055
165
esphome/components/audio/audio_transfer_buffer.cpp
Normal file
165
esphome/components/audio/audio_transfer_buffer.cpp
Normal file
@ -0,0 +1,165 @@
|
||||
#include "audio_transfer_buffer.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace audio {
|
||||
|
||||
AudioTransferBuffer::~AudioTransferBuffer() { this->deallocate_buffer_(); };
|
||||
|
||||
std::unique_ptr<AudioSinkTransferBuffer> AudioSinkTransferBuffer::create(size_t buffer_size) {
|
||||
std::unique_ptr<AudioSinkTransferBuffer> sink_buffer = make_unique<AudioSinkTransferBuffer>();
|
||||
|
||||
if (!sink_buffer->allocate_buffer_(buffer_size)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return sink_buffer;
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioSourceTransferBuffer> AudioSourceTransferBuffer::create(size_t buffer_size) {
|
||||
std::unique_ptr<AudioSourceTransferBuffer> source_buffer = make_unique<AudioSourceTransferBuffer>();
|
||||
|
||||
if (!source_buffer->allocate_buffer_(buffer_size)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return source_buffer;
|
||||
}
|
||||
|
||||
size_t AudioTransferBuffer::free() const {
|
||||
if (this->buffer_size_ == 0) {
|
||||
return 0;
|
||||
}
|
||||
return this->buffer_size_ - (this->buffer_length_ - (this->data_start_ - this->buffer_));
|
||||
}
|
||||
|
||||
void AudioTransferBuffer::decrease_buffer_length(size_t bytes) {
|
||||
this->buffer_length_ -= bytes;
|
||||
this->data_start_ += bytes;
|
||||
}
|
||||
|
||||
void AudioTransferBuffer::increase_buffer_length(size_t bytes) { this->buffer_length_ += bytes; }
|
||||
|
||||
void AudioTransferBuffer::clear_buffered_data() {
|
||||
this->buffer_length_ = 0;
|
||||
if (this->ring_buffer_.use_count() > 0) {
|
||||
this->ring_buffer_->reset();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioSinkTransferBuffer::clear_buffered_data() {
|
||||
this->buffer_length_ = 0;
|
||||
if (this->ring_buffer_.use_count() > 0) {
|
||||
this->ring_buffer_->reset();
|
||||
}
|
||||
#ifdef USE_SPEAKER
|
||||
if (this->speaker_ != nullptr) {
|
||||
this->speaker_->stop();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool AudioTransferBuffer::has_buffered_data() const {
|
||||
if (this->ring_buffer_.use_count() > 0) {
|
||||
return ((this->ring_buffer_->available() > 0) || (this->available() > 0));
|
||||
}
|
||||
return (this->available() > 0);
|
||||
}
|
||||
|
||||
bool AudioTransferBuffer::reallocate(size_t new_buffer_size) {
|
||||
if (this->buffer_length_ > 0) {
|
||||
// Already has data in the buffer, fail
|
||||
return false;
|
||||
}
|
||||
this->deallocate_buffer_();
|
||||
return this->allocate_buffer_(new_buffer_size);
|
||||
}
|
||||
|
||||
bool AudioTransferBuffer::allocate_buffer_(size_t buffer_size) {
|
||||
this->buffer_size_ = buffer_size;
|
||||
|
||||
RAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
|
||||
|
||||
this->buffer_ = allocator.allocate(this->buffer_size_);
|
||||
if (this->buffer_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this->data_start_ = this->buffer_;
|
||||
this->buffer_length_ = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioTransferBuffer::deallocate_buffer_() {
|
||||
if (this->buffer_ != nullptr) {
|
||||
RAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
|
||||
allocator.deallocate(this->buffer_, this->buffer_size_);
|
||||
this->buffer_ = nullptr;
|
||||
this->data_start_ = nullptr;
|
||||
}
|
||||
|
||||
this->buffer_size_ = 0;
|
||||
this->buffer_length_ = 0;
|
||||
}
|
||||
|
||||
size_t AudioSourceTransferBuffer::transfer_data_from_source(TickType_t ticks_to_wait) {
|
||||
// Shift data in buffer to start
|
||||
if (this->buffer_length_ > 0) {
|
||||
memmove(this->buffer_, this->data_start_, this->buffer_length_);
|
||||
}
|
||||
this->data_start_ = this->buffer_;
|
||||
|
||||
size_t bytes_to_read = this->free();
|
||||
size_t bytes_read = 0;
|
||||
if (bytes_to_read > 0) {
|
||||
if (this->ring_buffer_.use_count() > 0) {
|
||||
bytes_read = this->ring_buffer_->read((void *) this->get_buffer_end(), bytes_to_read, ticks_to_wait);
|
||||
}
|
||||
|
||||
this->increase_buffer_length(bytes_read);
|
||||
}
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
size_t AudioSinkTransferBuffer::transfer_data_to_sink(TickType_t ticks_to_wait) {
|
||||
size_t bytes_written = 0;
|
||||
if (this->available()) {
|
||||
#ifdef USE_SPEAKER
|
||||
if (this->speaker_ != nullptr) {
|
||||
bytes_written = this->speaker_->play(this->data_start_, this->available(), ticks_to_wait);
|
||||
} else
|
||||
#endif
|
||||
if (this->ring_buffer_.use_count() > 0) {
|
||||
bytes_written =
|
||||
this->ring_buffer_->write_without_replacement((void *) this->data_start_, this->available(), ticks_to_wait);
|
||||
}
|
||||
|
||||
this->decrease_buffer_length(bytes_written);
|
||||
|
||||
// Shift unwritten data to the start of the buffer
|
||||
memmove(this->buffer_, this->data_start_, this->buffer_length_);
|
||||
this->data_start_ = this->buffer_;
|
||||
}
|
||||
return bytes_written;
|
||||
}
|
||||
|
||||
bool AudioSinkTransferBuffer::has_buffered_data() const {
|
||||
#ifdef USE_SPEAKER
|
||||
if (this->speaker_ != nullptr) {
|
||||
return (this->speaker_->has_buffered_data() || (this->available() > 0));
|
||||
}
|
||||
#endif
|
||||
if (this->ring_buffer_.use_count() > 0) {
|
||||
return ((this->ring_buffer_->available() > 0) || (this->available() > 0));
|
||||
}
|
||||
return (this->available() > 0);
|
||||
}
|
||||
|
||||
} // namespace audio
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
139
esphome/components/audio/audio_transfer_buffer.h
Normal file
139
esphome/components/audio/audio_transfer_buffer.h
Normal file
@ -0,0 +1,139 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/ring_buffer.h"
|
||||
|
||||
#ifdef USE_SPEAKER
|
||||
#include "esphome/components/speaker/speaker.h"
|
||||
#endif
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace audio {
|
||||
|
||||
class AudioTransferBuffer {
|
||||
/*
|
||||
* @brief Class that facilitates tranferring data between a buffer and an audio source or sink.
|
||||
* The transfer buffer is a typical C array that temporarily holds data for processing in other audio components.
|
||||
* Both sink and source transfer buffers can use a ring buffer as the sink/source.
|
||||
* - The ring buffer is stored in a shared_ptr, so destroying the transfer buffer object will release ownership.
|
||||
*/
|
||||
public:
|
||||
/// @brief Destructor that deallocates the transfer buffer
|
||||
~AudioTransferBuffer();
|
||||
|
||||
/// @brief Returns a pointer to the start of the transfer buffer where available() bytes of exisiting data can be read
|
||||
uint8_t *get_buffer_start() const { return this->data_start_; }
|
||||
|
||||
/// @brief Returns a pointer to the end of the transfer buffer where free() bytes of new data can be written
|
||||
uint8_t *get_buffer_end() const { return this->data_start_ + this->buffer_length_; }
|
||||
|
||||
/// @brief Updates the internal state of the transfer buffer. This should be called after reading data
|
||||
/// @param bytes The number of bytes consumed/read
|
||||
void decrease_buffer_length(size_t bytes);
|
||||
|
||||
/// @brief Updates the internal state of the transfer buffer. This should be called after writing data
|
||||
/// @param bytes The number of bytes written
|
||||
void increase_buffer_length(size_t bytes);
|
||||
|
||||
/// @brief Returns the transfer buffer's currently available bytes to read
|
||||
size_t available() const { return this->buffer_length_; }
|
||||
|
||||
/// @brief Returns the transfer buffers allocated bytes
|
||||
size_t capacity() const { return this->buffer_size_; }
|
||||
|
||||
/// @brief Returns the transfer buffer's currrently free bytes available to write
|
||||
size_t free() const;
|
||||
|
||||
/// @brief Clears data in the transfer buffer and, if possible, the source/sink.
|
||||
virtual void clear_buffered_data();
|
||||
|
||||
/// @brief Tests if there is any data in the tranfer buffer or the source/sink.
|
||||
/// @return True if there is data, false otherwise.
|
||||
virtual bool has_buffered_data() const;
|
||||
|
||||
bool reallocate(size_t new_buffer_size);
|
||||
|
||||
protected:
|
||||
/// @brief Allocates the transfer buffer in external memory, if available.
|
||||
/// @return True is successful, false otherwise.
|
||||
bool allocate_buffer_(size_t buffer_size);
|
||||
|
||||
/// @brief Deallocates the buffer and resets the class variables.
|
||||
void deallocate_buffer_();
|
||||
|
||||
// A possible source or sink for the transfer buffer
|
||||
std::shared_ptr<RingBuffer> ring_buffer_;
|
||||
|
||||
uint8_t *buffer_{nullptr};
|
||||
uint8_t *data_start_{nullptr};
|
||||
|
||||
size_t buffer_size_{0};
|
||||
size_t buffer_length_{0};
|
||||
};
|
||||
|
||||
class AudioSinkTransferBuffer : public AudioTransferBuffer {
|
||||
/*
|
||||
* @brief A class that implements a transfer buffer for audio sinks.
|
||||
* Supports writing processed data in the transfer buffer to a ring buffer or a speaker component.
|
||||
*/
|
||||
public:
|
||||
/// @brief Creates a new sink transfer buffer.
|
||||
/// @param buffer_size Size of the transfer buffer in bytes.
|
||||
/// @return unique_ptr if successfully allocated, nullptr otherwise
|
||||
static std::unique_ptr<AudioSinkTransferBuffer> create(size_t buffer_size);
|
||||
|
||||
/// @brief Writes any available data in the transfer buffer to the sink.
|
||||
/// @param ticks_to_wait FreeRTOS ticks to block while waiting for the sink to have enough space
|
||||
/// @return Number of bytes written
|
||||
size_t transfer_data_to_sink(TickType_t ticks_to_wait);
|
||||
|
||||
/// @brief Adds a ring buffer as the transfer buffer's sink.
|
||||
/// @param ring_buffer weak_ptr to the allocated ring buffer
|
||||
void set_sink(const std::weak_ptr<RingBuffer> &ring_buffer) { this->ring_buffer_ = ring_buffer.lock(); }
|
||||
|
||||
#ifdef USE_SPEAKER
|
||||
/// @brief Adds a speaker as the transfer buffer's sink.
|
||||
/// @param speaker Pointer to the speaker component
|
||||
void set_sink(speaker::Speaker *speaker) { this->speaker_ = speaker; }
|
||||
#endif
|
||||
|
||||
void clear_buffered_data() override;
|
||||
|
||||
bool has_buffered_data() const override;
|
||||
|
||||
protected:
|
||||
#ifdef USE_SPEAKER
|
||||
speaker::Speaker *speaker_{nullptr};
|
||||
#endif
|
||||
};
|
||||
|
||||
class AudioSourceTransferBuffer : public AudioTransferBuffer {
|
||||
/*
|
||||
* @brief A class that implements a transfer buffer for audio sources.
|
||||
* Supports reading audio data from a ring buffer into the transfer buffer for processing.
|
||||
*/
|
||||
public:
|
||||
/// @brief Creates a new source transfer buffer.
|
||||
/// @param buffer_size Size of the transfer buffer in bytes.
|
||||
/// @return unique_ptr if successfully allocated, nullptr otherwise
|
||||
static std::unique_ptr<AudioSourceTransferBuffer> create(size_t buffer_size);
|
||||
|
||||
/// @brief Reads any available data from the sink into the transfer buffer.
|
||||
/// @param ticks_to_wait FreeRTOS ticks to block while waiting for the source to have enough data
|
||||
/// @return Number of bytes read
|
||||
size_t transfer_data_from_source(TickType_t ticks_to_wait);
|
||||
|
||||
/// @brief Adds a ring buffer as the transfer buffer's source.
|
||||
/// @param ring_buffer weak_ptr to the allocated ring buffer
|
||||
void set_source(const std::weak_ptr<RingBuffer> &ring_buffer) { this->ring_buffer_ = ring_buffer.lock(); };
|
||||
};
|
||||
|
||||
} // namespace audio
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
Loading…
x
Reference in New Issue
Block a user