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