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