parent
2ff8eac022
commit
f84474d94d
@ -0,0 +1,72 @@ |
||||
/*
|
||||
* Copyright 2024 jacqueline <me@jacqueline.id.au> |
||||
* |
||||
* SPDX-License-Identifier: GPL-3.0-only |
||||
*/ |
||||
|
||||
#pragma once |
||||
|
||||
#include <stdint.h> |
||||
#include <atomic> |
||||
#include <cstddef> |
||||
#include <span> |
||||
|
||||
#include "freertos/FreeRTOS.h" |
||||
|
||||
#include "freertos/ringbuf.h" |
||||
#include "portmacro.h" |
||||
|
||||
namespace drivers { |
||||
|
||||
/*
|
||||
* A circular buffer of signed, 16-bit PCM samples. PcmBuffers are the main |
||||
* data structure used for shuffling large amounts of read-to-play samples |
||||
* throughout the system. |
||||
*/ |
||||
class PcmBuffer { |
||||
public: |
||||
PcmBuffer(size_t size_in_samples); |
||||
~PcmBuffer(); |
||||
|
||||
/* Adds samples to the buffer. */ |
||||
auto send(std::span<const int16_t>) -> void; |
||||
|
||||
/*
|
||||
* Fills the given span with samples. If enough samples are available in |
||||
* the buffer, then the span will be filled with samples from the buffer. Any |
||||
* shortfall is made up by padding the given span with zeroes. |
||||
*/ |
||||
auto receive(std::span<int16_t>, bool isr) -> BaseType_t; |
||||
|
||||
auto clear() -> void; |
||||
auto isEmpty() -> bool; |
||||
|
||||
/*
|
||||
* How many samples have been added to this buffer since it was created. This |
||||
* method overflows by wrapping around to zero. |
||||
*/ |
||||
auto totalSent() -> uint32_t; |
||||
|
||||
/*
|
||||
* How many samples have been removed from this buffer since it was created. |
||||
* This method overflows by wrapping around to zero. |
||||
*/ |
||||
auto totalReceived() -> uint32_t; |
||||
|
||||
// Not copyable or movable.
|
||||
PcmBuffer(const PcmBuffer&) = delete; |
||||
PcmBuffer& operator=(const PcmBuffer&) = delete; |
||||
|
||||
private: |
||||
auto readSingle(std::span<int16_t>, bool isr) |
||||
-> std::pair<size_t, BaseType_t>; |
||||
|
||||
StaticRingbuffer_t meta_; |
||||
uint8_t* buf_; |
||||
|
||||
std::atomic<uint32_t> sent_; |
||||
std::atomic<uint32_t> received_; |
||||
RingbufHandle_t ringbuf_; |
||||
}; |
||||
|
||||
} // namespace drivers
|
@ -0,0 +1,118 @@ |
||||
/*
|
||||
* Copyright 2024 jacqueline <me@jacqueline.id.au> |
||||
* |
||||
* SPDX-License-Identifier: GPL-3.0-only |
||||
*/ |
||||
|
||||
#include "drivers/pcm_buffer.hpp" |
||||
#include <stdint.h> |
||||
|
||||
#include <algorithm> |
||||
#include <cstddef> |
||||
#include <cstring> |
||||
#include <span> |
||||
#include <tuple> |
||||
|
||||
#include "esp_log.h" |
||||
#include "freertos/FreeRTOS.h" |
||||
|
||||
#include "esp_heap_caps.h" |
||||
#include "freertos/ringbuf.h" |
||||
#include "portmacro.h" |
||||
|
||||
namespace drivers { |
||||
|
||||
[[maybe_unused]] static const char kTag[] = "pcmbuf"; |
||||
|
||||
PcmBuffer::PcmBuffer(size_t size_in_samples) : sent_(0), received_(0) { |
||||
size_t size_in_bytes = size_in_samples * sizeof(int16_t); |
||||
ESP_LOGI(kTag, "allocating pcm buffer of size %u (%uKiB)", size_in_samples, |
||||
size_in_bytes / 1024); |
||||
buf_ = reinterpret_cast<uint8_t*>( |
||||
heap_caps_malloc(size_in_bytes, MALLOC_CAP_SPIRAM)); |
||||
ringbuf_ = xRingbufferCreateStatic(size_in_bytes, RINGBUF_TYPE_BYTEBUF, buf_, |
||||
&meta_); |
||||
} |
||||
|
||||
PcmBuffer::~PcmBuffer() { |
||||
vRingbufferDelete(ringbuf_); |
||||
heap_caps_free(buf_); |
||||
} |
||||
|
||||
auto PcmBuffer::send(std::span<const int16_t> data) -> void { |
||||
xRingbufferSend(ringbuf_, data.data(), data.size_bytes(), portMAX_DELAY); |
||||
sent_ += data.size(); |
||||
} |
||||
|
||||
IRAM_ATTR auto PcmBuffer::receive(std::span<int16_t> dest, bool isr) |
||||
-> BaseType_t { |
||||
size_t first_read = 0, second_read = 0; |
||||
BaseType_t ret1 = false, ret2 = false; |
||||
std::tie(first_read, ret1) = readSingle(dest, isr); |
||||
|
||||
if (first_read < dest.size()) { |
||||
std::tie(second_read, ret2) = readSingle(dest.subspan(first_read), isr); |
||||
} |
||||
|
||||
size_t total_read = first_read + second_read; |
||||
if (total_read < dest.size()) { |
||||
std::fill_n(dest.begin() + total_read, dest.size() - total_read, 0); |
||||
} |
||||
|
||||
received_ += first_read + second_read; |
||||
|
||||
return ret1 || ret2; |
||||
} |
||||
|
||||
auto PcmBuffer::clear() -> void { |
||||
while (!isEmpty()) { |
||||
size_t bytes_cleared; |
||||
void* data = xRingbufferReceive(ringbuf_, &bytes_cleared, 0); |
||||
vRingbufferReturnItem(ringbuf_, data); |
||||
received_ += bytes_cleared / sizeof(int16_t); |
||||
} |
||||
} |
||||
|
||||
auto PcmBuffer::isEmpty() -> bool { |
||||
return xRingbufferGetMaxItemSize(ringbuf_) == |
||||
xRingbufferGetCurFreeSize(ringbuf_); |
||||
} |
||||
|
||||
auto PcmBuffer::totalSent() -> uint32_t { |
||||
return sent_; |
||||
} |
||||
|
||||
auto PcmBuffer::totalReceived() -> uint32_t { |
||||
return received_; |
||||
} |
||||
|
||||
IRAM_ATTR auto PcmBuffer::readSingle(std::span<int16_t> dest, bool isr) |
||||
-> std::pair<size_t, BaseType_t> { |
||||
BaseType_t ret; |
||||
size_t read_bytes = 0; |
||||
void* data; |
||||
if (isr) { |
||||
data = |
||||
xRingbufferReceiveUpToFromISR(ringbuf_, &read_bytes, dest.size_bytes()); |
||||
} else { |
||||
data = xRingbufferReceiveUpTo(ringbuf_, &read_bytes, 0, dest.size_bytes()); |
||||
} |
||||
|
||||
size_t read_samples = read_bytes / sizeof(int16_t); |
||||
|
||||
if (!data) { |
||||
return {read_samples, ret}; |
||||
} |
||||
|
||||
std::memcpy(dest.data(), data, read_bytes); |
||||
|
||||
if (isr) { |
||||
vRingbufferReturnItem(ringbuf_, data); |
||||
} else { |
||||
vRingbufferReturnItemFromISR(ringbuf_, data, &ret); |
||||
} |
||||
|
||||
return {read_samples, ret}; |
||||
} |
||||
|
||||
} // namespace drivers
|
Loading…
Reference in new issue