summaryrefslogtreecommitdiff
path: root/src/drivers/pcm_buffer.cpp
blob: 071f5ceaee7126140cc7810b31ba6a5558492699 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/*
 * 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/projdefs.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) -> size_t {
  if (!xRingbufferSend(ringbuf_, data.data(), data.size_bytes(),
                       pdMS_TO_TICKS(100))) {
    return 0;
  }
  sent_ += data.size();
  return 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 = 0;
    void* data = xRingbufferReceive(ringbuf_, &bytes_cleared, 0);
    if (data) {
      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