mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	[audio] Media Player Components PR3 (#8165)
This commit is contained in:
		
							
								
								
									
										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 | ||||||
		Reference in New Issue
	
	Block a user