arduino-audio-tools
MiniAudioStream.h
1 #pragma once
8 #include "AudioTools.h"
9 #include <mutex>
10 
11 #define MINIAUDIO_IMPLEMENTATION
12 #include "miniaudio.h"
13 
14 #define MA_BUFFER_COUNT 100
15 #define MA_START_COUNT MA_BUFFER_COUNT - 2
16 #define MA_DELAY 10
17 
18 namespace audio_tools {
19 
25 class MiniAudioConfig : public AudioInfo {
26  public:
27  MiniAudioConfig() {
28  sample_rate = 44100;
29  channels = 2;
30  bits_per_sample = 16;
31  };
32  MiniAudioConfig(const MiniAudioConfig &) = default;
33  MiniAudioConfig(const AudioInfo &in) {
35  channels = in.channels;
37  }
38 
39  bool is_input = false;
40  bool is_output = true;
41  int delay_ms_if_buffer_full = MA_DELAY;
42  int buffer_count = MA_BUFFER_COUNT;
43  int buffer_start_count = MA_START_COUNT;
44 };
45 
52 class MiniAudioStream : public AudioStream {
53  public:
54  MiniAudioStream() = default;
55  ~MiniAudioStream() { end(); };
56 
57  MiniAudioConfig defaultConfig(RxTxMode mode = RXTX_MODE) {
58  MiniAudioConfig info;
59  info.sample_rate = 44100;
60  info.channels = 2;
61  info.bits_per_sample = 16;
62  switch (mode) {
63  case RX_MODE:
64  info.is_input = true;
65  info.is_output = false;
66  break;
67  case TX_MODE:
68  info.is_input = false;
69  info.is_output = true;
70  break;
71  case RXTX_MODE:
72  info.is_input = true;
73  info.is_output = true;
74  break;
75  default:
76  info.is_input = false;
77  info.is_output = false;
78  break;
79  }
80  return info;
81  }
82 
83  void setAudioInfo(AudioInfo in) override {
85  if (in.sample_rate != config.sample_rate ||
86  in.channels != config.channels ||
87  in.bits_per_sample != config.bits_per_sample) {
88  config.copyFrom(in);
89  if (is_active) {
90  is_active = false;
91  is_playing = false;
92  // This will stop the device so no need to do that manually.
93  ma_device_uninit(&device_ma);
94 
95  begin();
96  }
97  }
98  }
99 
100  bool begin(MiniAudioConfig info) {
102  this->config = info;
103  return begin();
104  }
105 
106  bool begin() override {
107  TRACEI();
108  if (config.is_output && !config.is_input)
109  config_ma = ma_device_config_init(ma_device_type_playback);
110  else if (!config.is_output && config.is_input)
111  config_ma = ma_device_config_init(ma_device_type_capture);
112  else if (config.is_output && config.is_input)
113  config_ma = ma_device_config_init(ma_device_type_duplex);
114  else if (!config.is_output && !config.is_input)
115  config_ma = ma_device_config_init(ma_device_type_loopback);
116 
117  config_ma.pUserData = this;
118  config_ma.playback.channels = config.channels;
119  config_ma.sampleRate = config.sample_rate;
120  config_ma.dataCallback = data_callback;
121  switch (config.bits_per_sample) {
122  case 8:
123  config_ma.playback.format = ma_format_u8;
124  break;
125  case 16:
126  config_ma.playback.format = ma_format_s16;
127  break;
128  case 24:
129  config_ma.playback.format = ma_format_s24;
130  break;
131  case 32:
132  config_ma.playback.format = ma_format_s32;
133  break;
134  default:
135  LOGE("Invalid format");
136  return false;
137  }
138 
139  if (ma_device_init(NULL, &config_ma, &device_ma) != MA_SUCCESS) {
140  // Failed to initialize the device.
141  return false;
142  }
143 
144  // The device is sleeping by default so you'll need to start it manually.
145  if (ma_device_start(&device_ma) != MA_SUCCESS) {
146  // Failed to initialize the device.
147  return false;
148  }
149 
150  is_active = true;
151  return is_active;
152  }
153 
154  void end() override {
155  is_active = false;
156  is_playing = false;
157  // This will stop the device so no need to do that manually.
158  ma_device_uninit(&device_ma);
159  // release buffer memory
160  buffer_in.resize(0);
161  buffer_out.resize(0);
162  }
163 
164  int availableForWrite() override {
165  return buffer_out.size() == 0 ? 0 : DEFAULT_BUFFER_SIZE;
166  }
167 
168  size_t write(const uint8_t *data, size_t len) override {
169  if (buffer_out.size() == 0) return 0;
170  LOGD("write: %zu", len);
171 
172  // write data to buffer
173  int open = len;
174  int written = 0;
175  while (open > 0) {
176  size_t result = 0;
177  {
178  std::lock_guard<std::mutex> guard(write_mtx);
179  result = buffer_out.writeArray(data + written, open);
180  open -= result;
181  written += result;
182  }
183 
184  if (result != len) doWait();
185  }
186 
187  // activate playing
188  // if (!is_playing && buffer_out.bufferCountFilled()>=MA_START_COUNT) {
189  if (!is_playing && buffer_out.available() >= config.buffer_start_count * buffer_size) {
190  LOGI("starting audio");
191  is_playing = true;
192  }
193  // std::this_thread::yield();
194  return written;
195  }
196 
197  int available() override {
198  return buffer_in.size() == 0 ? 0 : buffer_in.available();
199  }
200 
201  size_t readBytes(uint8_t *data, size_t len) override {
202  if (buffer_in.size() == 0) return 0;
203  LOGD("write: %zu", len);
204  std::lock_guard<std::mutex> guard(read_mtx);
205  return buffer_in.readArray(data, len);
206  }
207 
208  protected:
209  MiniAudioConfig config;
210  ma_device_config config_ma;
211  ma_device device_ma;
212  bool is_playing = false;
213  bool is_active = false;
214  bool is_buffers_setup = false;
215  RingBuffer<uint8_t> buffer_out{0};
216  RingBuffer<uint8_t> buffer_in{0};
217  std::mutex write_mtx;
218  std::mutex read_mtx;
219  int buffer_size = 0;
220 
221  // In playback mode copy data to pOutput. In capture mode read data from
222  // pInput. In full-duplex mode, both pOutput and pInput will be valid and
223  // you can move data from pInput into pOutput. Never process more than
224  // frameCount frames.
225 
226  void setupBuffers(int size) {
227  if (is_buffers_setup) return;
228  buffer_size = size;
229  int buffer_count = config.buffer_count;
230  LOGI("setupBuffers: %d * %d", size, buffer_count);
231  if (buffer_out.size() == 0 && config.is_output)
232  buffer_out.resize(size * buffer_count);
233  if (buffer_in.size() == 0 && config.is_input)
234  buffer_in.resize(size * buffer_count);
235  is_buffers_setup = true;
236  }
237 
238  void doWait() {
239  //std::this_thread::yield();
240  delay(config.delay_ms_if_buffer_full);
241  //std::this_thread::sleep_for (std::chrono::milliseconds(MA_DELAY));
242  }
243 
244  static void data_callback(ma_device *pDevice, void *pOutput,
245  const void *pInput, ma_uint32 frameCount) {
246  MiniAudioStream *self = (MiniAudioStream *)pDevice->pUserData;
247  AudioInfo cfg = self->audioInfo();
248 
249  int bytes = frameCount * cfg.channels * cfg.bits_per_sample / 8;
250  self->setupBuffers(bytes);
251 
252  if (pInput) {
253  int open = bytes;
254  int processed = 0;
255  while (open > 0) {
256  int len = 0;
257  {
258  std::unique_lock<mutex> guard(self->read_mtx);
259  int len =
260  self->buffer_in.writeArray((uint8_t *)pInput + processed, open);
261  open -= len;
262  processed += len;
263  }
264  if (len == 0) self->doWait();
265  }
266  }
267 
268  if (pOutput) {
269  memset(pOutput, 0, bytes);
270  if (self->is_playing) {
271  int open = bytes;
272  int processed = 0;
273  while (open > 0) {
274  size_t len = 0;
275  {
276  std::lock_guard<std::mutex> guard(self->write_mtx);
277  len = self->buffer_out.readArray((uint8_t *)pOutput + processed,
278  bytes);
279  open -= len;
280  processed += len;
281  }
282  if (len != bytes) self->doWait();
283  }
284  }
285  }
286  }
287 };
288 
289 } // namespace audio_tools
Base class for all Audio Streams. It support the boolean operator to test if the object is ready with...
Definition: BaseStream.h:109
virtual void setAudioInfo(AudioInfo newInfo) override
Defines the input AudioInfo.
Definition: BaseStream.h:117
virtual int readArray(T data[], int len)
reads multiple values
Definition: Buffers.h:41
virtual int writeArray(const T data[], int len)
Fills the buffer data.
Definition: Buffers.h:65
Configuration for MiniAudio.
Definition: MiniAudioStream.h:25
MiniAudio: https://miniaud.io/.
Definition: MiniAudioStream.h:52
void setAudioInfo(AudioInfo in) override
Defines the input AudioInfo.
Definition: MiniAudioStream.h:83
virtual int available()
provides the number of entries that are available to read
Definition: Buffers.h:366
virtual size_t size()
Returns the maximum capacity of the buffer.
Definition: Buffers.h:383
RxTxMode
The Microcontroller is the Audio Source (TX_MODE) or Audio Sink (RX_MODE). RXTX_MODE is Source and Si...
Definition: AudioTypes.h:28
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition: AudioConfig.h:823
Basic Audio information which drives e.g. I2S.
Definition: AudioTypes.h:52
void copyFrom(AudioInfo info)
Same as set.
Definition: AudioTypes.h:107
sample_rate_t sample_rate
Sample Rate: e.g 44100.
Definition: AudioTypes.h:55
uint16_t channels
Number of channels: 2=stereo, 1=mono.
Definition: AudioTypes.h:57
uint8_t bits_per_sample
Number of bits per sample (int16_t = 16 bits)
Definition: AudioTypes.h:59