arduino-audio-tools
Loading...
Searching...
No Matches
MiniAudioStream.h
Go to the documentation of this file.
1#pragma once
8#include <atomic>
9#include <mutex>
10
11#include "AudioTools.h"
12
13#define MINIAUDIO_IMPLEMENTATION
14#include "miniaudio.h"
15
16#define MA_BUFFER_COUNT 10
17#define MA_BUFFER_SIZE 1200
18#define MA_START_COUNT 2
19#define MA_DELAY 10
20
21namespace audio_tools {
22
28class MiniAudioConfig : public AudioInfo {
29 public:
31 sample_rate = 44100;
32 channels = 2;
33 bits_per_sample = 16;
34 };
41
42 bool is_input = false;
43 bool is_output = true;
49 true; // Automatically restart after buffer underrun
50 int underrun_tolerance = 5; // Number of empty reads before stopping playback
51};
52
64 public:
65 MiniAudioStream() = default;
67
70 info.sample_rate = 44100;
71 info.channels = 2;
73 switch (mode) {
74 case RX_MODE:
75 info.is_input = true;
76 info.is_output = false;
77 break;
78 case TX_MODE:
79 info.is_input = false;
80 info.is_output = true;
81 break;
82 case RXTX_MODE:
83 info.is_input = true;
84 info.is_output = true;
85 break;
86 default:
87 info.is_input = false;
88 info.is_output = false;
89 break;
90 }
91 return info;
92 }
93
94 void setAudioInfo(AudioInfo in) override {
96 if (in.sample_rate != config.sample_rate ||
97 in.channels != config.channels ||
99 config.copyFrom(in);
100 if (is_active.load()) {
101 is_active.store(false);
102 is_playing.store(false);
103 // This will stop the device so no need to do that manually.
105
106 begin();
107 }
108 }
109 }
110
113 this->config = info;
114 return begin();
115 }
116
117 bool begin() override {
118 TRACEI();
122 else if (!config.is_output && config.is_input)
124 else if (config.is_output && config.is_input)
126 else if (!config.is_output && !config.is_input)
128
129 config_ma.pUserData = this;
130 config_ma.playback.channels = config.channels;
131 config_ma.sampleRate = config.sample_rate;
132 config_ma.dataCallback = data_callback;
133 switch (config.bits_per_sample) {
134 case 8:
135 config_ma.playback.format = ma_format_u8;
136 break;
137 case 16:
138 config_ma.playback.format = ma_format_s16;
139 break;
140 case 24:
141 config_ma.playback.format = ma_format_s24;
142 break;
143 case 32:
144 config_ma.playback.format = ma_format_s32;
145 break;
146 default:
147 LOGE("Invalid format");
148 return false;
149 }
150
152 // Failed to initialize the device.
153 return false;
154 }
155
156 // The device is sleeping by default so you'll need to start it manually.
158 // Failed to initialize the device.
160 return false;
161 }
162
163 is_active.store(true);
164 return is_active.load();
165 }
166
167 void end() override {
168 is_active.store(false);
169 is_playing.store(false);
170 // This will stop the device so no need to do that manually.
172 // release buffer memory
173 buffer_in.resize(0);
175 is_buffers_setup.store(false);
176 }
177
178 int availableForWrite() override {
179 return buffer_out.size() == 0 ? 0 : DEFAULT_BUFFER_SIZE;
180 }
181
182 size_t write(const uint8_t* data, size_t len) override {
183 // Input validation
184 if (!data || len == 0) {
185 LOGW("Invalid write parameters: data=%p, len=%zu", data, len);
186 return 0;
187 }
188
189 if (buffer_out.size() == 0) {
190 LOGW("Output buffer not initialized");
191 return 0;
192 }
193
194 if (!is_active.load()) {
195 LOGW("Stream not active");
196 return 0;
197 }
198
199 LOGD("write: %zu", len);
200
201 // write data to buffer
202 int open = len;
203 int written = 0;
204 int retry_count = 0;
205 const int max_retries = 1000; // Prevent infinite loops
206
207 while (open > 0 && retry_count < max_retries) {
208 size_t result = 0;
209 {
210 std::lock_guard<std::mutex> guard(write_mtx);
211 result = buffer_out.writeArray(data + written, open);
212 open -= result;
213 written += result;
214 }
215
216 if (result == 0) {
217 retry_count++;
218 doWait();
219 } else {
220 retry_count = 0; // Reset on successful write
221 }
222 }
223
224 if (retry_count >= max_retries) {
225 LOGE("Write timeout after %d retries, written %d of %zu bytes",
226 max_retries, written, len);
227 }
228
229 // activate playing
230 // if (!is_playing && buffer_out.bufferCountFilled()>=MA_START_COUNT) {
231 int current_buffer_size = buffer_size.load();
232 bool should_start_playing = false;
233
234 // Start playing if we have enough data and either:
235 // 1. We're not playing yet, or
236 // 2. We stopped due to buffer underrun but now have data again
237 if (current_buffer_size > 0) {
240
241 if (!is_playing.load() && available_data >= threshold) {
243 } else if (is_playing.load() && available_data == 0) {
244 // Stop playing if buffer is completely empty (helps with long delays)
245 LOGW("Buffer empty, pausing playback");
246 is_playing.store(false);
247 }
248 }
249
251 LOGI("starting audio playback");
252 is_playing.store(true);
253 }
254
255 // std::this_thread::yield();
256 return written;
257 }
258
259 int available() override {
260 return buffer_in.size() == 0 ? 0 : buffer_in.available();
261 }
262
263 size_t readBytes(uint8_t* data, size_t len) override {
264 if (!data || len == 0) {
265 LOGW("Invalid read parameters: data=%p, len=%zu", data, len);
266 return 0;
267 }
268
269 if (buffer_in.size() == 0) {
270 LOGW("Input buffer not initialized");
271 return 0;
272 }
273
274 if (!is_active.load()) {
275 LOGW("Stream not active");
276 return 0;
277 }
278
279 LOGD("read: %zu", len);
280 std::lock_guard<std::mutex> guard(read_mtx);
281 return buffer_in.readArray(data, len);
282 }
283
286 if (!is_active.load()) {
287 LOGW("Cannot restart playback - stream not active");
288 return;
289 }
290
291 int current_buffer_size = buffer_size.load();
292 if (current_buffer_size > 0 && buffer_out.available() > 0) {
293 LOGI("Manually restarting playback");
294 is_playing.store(true);
295 } else {
296 LOGW("Cannot restart playback - no data available");
297 }
298 }
299
301 bool isPlaying() const { return is_playing.load(); }
302
303 protected:
307 std::atomic<bool> is_playing{false};
308 std::atomic<bool> is_active{false};
309 std::atomic<bool> is_buffers_setup{false};
312 std::mutex write_mtx;
313 std::mutex read_mtx;
314 std::atomic<int> buffer_size{0};
315
316 // In playback mode copy data to pOutput. In capture mode read data from
317 // pInput. In full-duplex mode, both pOutput and pInput will be valid and
318 // you can move data from pInput into pOutput. Never process more than
319 // frameCount frames.
320
321 void setupBuffers(int size = MA_BUFFER_SIZE) {
322 std::lock_guard<std::mutex> guard(write_mtx);
323 if (is_buffers_setup.load()) return;
324
325 // Validate buffer size
326 if (size <= 0 || size > 1024 * 1024) { // Max 1MB per buffer chunk
327 LOGE("Invalid buffer size: %d", size);
328 return;
329 }
330
331 buffer_size.store(size);
332 int buffer_count = config.buffer_count;
333
334 // Validate total buffer size to prevent excessive memory allocation
335 size_t total_size = static_cast<size_t>(size) * buffer_count;
336 if (total_size > 100 * 1024 * 1024) { // Max 100MB total
337 LOGE("Buffer size too large: %zu bytes", total_size);
338 return;
339 }
340
341 LOGI("setupBuffers: %d * %d = %zu bytes", size, buffer_count, total_size);
342
343 if (buffer_out.size() == 0 && config.is_output) {
344 if (!buffer_out.resize(size * buffer_count)) {
345 LOGE("Failed to resize output buffer");
346 return;
347 }
348 }
349 if (buffer_in.size() == 0 && config.is_input) {
350 if (!buffer_in.resize(size * buffer_count)) {
351 LOGE("Failed to resize input buffer");
352 return;
353 }
354 }
355 is_buffers_setup.store(true);
356 }
357
358 void doWait() {
359 // std::this_thread::yield();
361 // std::this_thread::sleep_for (std::chrono::milliseconds(MA_DELAY));
362 }
363
365 const void* pInput, ma_uint32 frameCount) {
366 MiniAudioStream* self = (MiniAudioStream*)pDevice->pUserData;
367 if (!self || !self->is_active.load()) {
368 return; // Safety check
369 }
370
371 AudioInfo cfg = self->audioInfo();
372 if (cfg.channels == 0 || cfg.bits_per_sample == 0) {
373 LOGE("Invalid audio configuration in callback");
374 return;
375 }
376
377 int bytes = frameCount * cfg.channels * cfg.bits_per_sample / 8;
378 if (bytes <= 0 || bytes > 1024 * 1024) { // Sanity check
379 LOGE("Invalid byte count in callback: %d", bytes);
380 return;
381 }
382
383 self->setupBuffers(bytes);
384
385 if (pInput && self->buffer_in.size() > 0) {
386 int open = bytes;
387 int processed = 0;
388 int retry_count = 0;
389 const int max_retries = 100;
390
391 while (open > 0 && retry_count < max_retries && self->is_active.load()) {
392 int len = 0;
393 {
394 std::unique_lock<std::mutex> guard(self->read_mtx);
395 len = self->buffer_in.writeArray((uint8_t*)pInput + processed, open);
396 open -= len;
397 processed += len;
398 }
399 if (len == 0) {
400 retry_count++;
401 self->doWait();
402 } else {
403 retry_count = 0;
404 }
405 }
406 }
407
408 if (pOutput) {
409 memset(pOutput, 0, bytes);
410 if (self->is_playing.load() && self->buffer_out.size() > 0) {
411 int open = bytes;
412 int processed = 0;
413 int consecutive_failures = 0;
414 const int max_failures = self->config.underrun_tolerance;
415
416 while (open > 0 && self->is_active.load()) {
417 size_t len = 0;
418 {
419 std::lock_guard<std::mutex> guard(self->write_mtx);
420 len =
421 self->buffer_out.readArray((uint8_t*)pOutput + processed, open);
422 open -= len;
423 processed += len;
424 }
425
426 if (len == 0) {
428 // If we can't get data for too long, stop playing to prevent issues
431 LOGW("Buffer underrun detected, stopping playback");
432 self->is_playing.store(false);
433 break;
434 }
435 // Don't wait in callback for too long - just output silence
436 break;
437 } else {
439 }
440 }
441 }
442 }
443 }
444};
445
446} // namespace audio_tools
#define LOGW(...)
Definition AudioLoggerIDF.h:29
#define TRACEI()
Definition AudioLoggerIDF.h:32
#define LOGI(...)
Definition AudioLoggerIDF.h:28
#define LOGD(...)
Definition AudioLoggerIDF.h:27
#define LOGE(...)
Definition AudioLoggerIDF.h:30
#define MA_DELAY
Definition MiniAudioStream.h:19
#define MA_BUFFER_SIZE
Definition MiniAudioStream.h:17
#define MA_BUFFER_COUNT
Definition MiniAudioStream.h:16
#define MA_START_COUNT
Definition MiniAudioStream.h:18
#define DEFAULT_BUFFER_SIZE
Definition avr.h:20
Base class for all Audio Streams. It support the boolean operator to test if the object is ready with...
Definition BaseStream.h:120
AudioInfo info
Definition BaseStream.h:171
virtual void setAudioInfo(AudioInfo newInfo) override
Defines the input AudioInfo.
Definition BaseStream.h:128
virtual AudioInfo audioInfo() override
provides the actual input AudioInfo
Definition BaseStream.h:151
virtual int readArray(T data[], int len)
reads multiple values
Definition Buffers.h:34
virtual int writeArray(const T data[], int len)
Fills the buffer data.
Definition Buffers.h:56
Configuration for MiniAudio.
Definition MiniAudioStream.h:28
int underrun_tolerance
Definition MiniAudioStream.h:50
int buffer_count
Definition MiniAudioStream.h:46
MiniAudioConfig()
Definition MiniAudioStream.h:30
int delay_ms_if_buffer_full
Definition MiniAudioStream.h:44
MiniAudioConfig(const AudioInfo &in)
Definition MiniAudioStream.h:36
bool auto_restart_on_underrun
Definition MiniAudioStream.h:48
bool is_output
Definition MiniAudioStream.h:43
bool is_input
Definition MiniAudioStream.h:42
MiniAudioConfig(const MiniAudioConfig &)=default
int buffer_start_count
Definition MiniAudioStream.h:47
int buffer_size
Definition MiniAudioStream.h:45
MiniAudio: https://miniaud.io/.
Definition MiniAudioStream.h:63
std::mutex read_mtx
Definition MiniAudioStream.h:313
bool isPlaying() const
Check if playback is currently active.
Definition MiniAudioStream.h:301
void setupBuffers(int size=1200)
Definition MiniAudioStream.h:321
std::mutex write_mtx
Definition MiniAudioStream.h:312
size_t readBytes(uint8_t *data, size_t len) override
Definition MiniAudioStream.h:263
std::atomic< int > buffer_size
Definition MiniAudioStream.h:314
std::atomic< bool > is_active
Definition MiniAudioStream.h:308
void restartPlayback()
Manually restart playback (useful after long delays)
Definition MiniAudioStream.h:285
void end() override
Definition MiniAudioStream.h:167
int available() override
Definition MiniAudioStream.h:259
size_t write(const uint8_t *data, size_t len) override
Definition MiniAudioStream.h:182
std::atomic< bool > is_playing
Definition MiniAudioStream.h:307
ma_device device_ma
Definition MiniAudioStream.h:306
int availableForWrite() override
Definition MiniAudioStream.h:178
void doWait()
Definition MiniAudioStream.h:358
void setAudioInfo(AudioInfo in) override
Defines the input AudioInfo.
Definition MiniAudioStream.h:94
bool begin() override
Definition MiniAudioStream.h:117
RingBuffer< uint8_t > buffer_in
Definition MiniAudioStream.h:311
RingBuffer< uint8_t > buffer_out
Definition MiniAudioStream.h:310
ma_device_config config_ma
Definition MiniAudioStream.h:305
MiniAudioConfig defaultConfig(RxTxMode mode=RXTX_MODE)
Definition MiniAudioStream.h:68
MiniAudioConfig config
Definition MiniAudioStream.h:304
~MiniAudioStream()
Definition MiniAudioStream.h:66
std::atomic< bool > is_buffers_setup
Definition MiniAudioStream.h:309
bool begin(MiniAudioConfig info)
Definition MiniAudioStream.h:111
static void data_callback(ma_device *pDevice, void *pOutput, const void *pInput, ma_uint32 frameCount)
Definition MiniAudioStream.h:364
Implements a typed Ringbuffer.
Definition Buffers.h:353
virtual size_t size() override
Returns the maximum capacity of the buffer.
Definition Buffers.h:440
virtual bool resize(size_t len)
Resizes the buffer if supported: returns false if not supported.
Definition Buffers.h:430
virtual int available() override
provides the number of entries that are available to read
Definition Buffers.h:422
RxTxMode
The Microcontroller is the Audio Source (TX_MODE) or Audio Sink (RX_MODE). RXTX_MODE is Source and Si...
Definition AudioTypes.h:26
@ RXTX_MODE
Definition AudioTypes.h:26
@ TX_MODE
Definition AudioTypes.h:26
@ RX_MODE
Definition AudioTypes.h:26
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition AudioCodecsBase.h:10
void delay(uint32_t ms)
Definition Arduino.h:255
size_t writeData(Print *p_out, T *data, int samples, int maxSamples=512)
Definition AudioTypes.h:508
Basic Audio information which drives e.g. I2S.
Definition AudioTypes.h:51
void copyFrom(AudioInfo info)
Same as set.
Definition AudioTypes.h:101
sample_rate_t sample_rate
Sample Rate: e.g 44100.
Definition AudioTypes.h:53
uint16_t channels
Number of channels: 2=stereo, 1=mono.
Definition AudioTypes.h:55
uint8_t bits_per_sample
Number of bits per sample (int16_t = 16 bits)
Definition AudioTypes.h:57