70 info.is_output =
false;
73 info.is_input =
false;
74 info.is_output =
true;
78 info.is_output =
true;
81 info.is_input =
false;
82 info.is_output =
false;
94 if (is_active.load()) {
95 is_active.store(
false);
96 is_playing.store(
false);
98 ma_device_uninit(&device_ma);
111 bool begin()
override {
113 setupBuffers(config.buffer_size);
114 if (config.is_output && !config.is_input)
115 config_ma = ma_device_config_init(ma_device_type_playback);
116 else if (!config.is_output && config.is_input)
117 config_ma = ma_device_config_init(ma_device_type_capture);
118 else if (config.is_output && config.is_input)
119 config_ma = ma_device_config_init(ma_device_type_duplex);
120 else if (!config.is_output && !config.is_input)
121 config_ma = ma_device_config_init(ma_device_type_loopback);
123 config_ma.pUserData =
this;
124 config_ma.playback.channels = config.
channels;
126 config_ma.dataCallback = data_callback;
129 config_ma.playback.format = ma_format_u8;
132 config_ma.playback.format = ma_format_s16;
135 config_ma.playback.format = ma_format_s24;
138 config_ma.playback.format = ma_format_s32;
141 LOGE(
"Invalid format");
145 if (ma_device_init(NULL, &config_ma, &device_ma) != MA_SUCCESS) {
151 if (ma_device_start(&device_ma) != MA_SUCCESS) {
153 ma_device_uninit(&device_ma);
157 is_active.store(
true);
158 return is_active.load();
161 void end()
override {
162 is_active.store(
false);
163 is_playing.store(
false);
165 ma_device_uninit(&device_ma);
169 is_buffers_setup.store(
false);
172 int availableForWrite()
override {
173 return buffer_out.
size() == 0 ? 0 : DEFAULT_BUFFER_SIZE;
176 size_t write(
const uint8_t *data,
size_t len)
override {
178 if (!data || len == 0) {
179 LOGW(
"Invalid write parameters: data=%p, len=%zu", data, len);
183 if (buffer_out.
size() == 0) {
184 LOGW(
"Output buffer not initialized");
188 if (!is_active.load()) {
189 LOGW(
"Stream not active");
193 LOGD(
"write: %zu", len);
199 const int max_retries = 1000;
201 while (open > 0 && retry_count < max_retries) {
204 std::lock_guard<std::mutex> guard(write_mtx);
205 result = buffer_out.
writeArray(data + written, open);
218 if (retry_count >= max_retries) {
219 LOGE(
"Write timeout after %d retries, written %d of %zu bytes", max_retries, written, len);
224 int current_buffer_size = buffer_size.load();
225 bool should_start_playing =
false;
230 if (current_buffer_size > 0) {
231 int available_data = buffer_out.
available();
232 int threshold = config.buffer_start_count * current_buffer_size;
234 if (!is_playing.load() && available_data >= threshold) {
235 should_start_playing =
true;
236 }
else if (is_playing.load() && available_data == 0) {
238 LOGW(
"Buffer empty, pausing playback");
239 is_playing.store(
false);
243 if (should_start_playing) {
244 LOGI(
"starting audio playback");
245 is_playing.store(
true);
252 int available()
override {
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);
262 if (buffer_in.
size() == 0) {
263 LOGW(
"Input buffer not initialized");
267 if (!is_active.load()) {
268 LOGW(
"Stream not active");
272 LOGD(
"read: %zu", len);
273 std::lock_guard<std::mutex> guard(read_mtx);
279 if (!is_active.load()) {
280 LOGW(
"Cannot restart playback - stream not active");
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);
289 LOGW(
"Cannot restart playback - no data available");
295 return is_playing.load();
300 ma_device_config config_ma;
302 std::atomic<bool> is_playing{
false};
303 std::atomic<bool> is_active{
false};
304 std::atomic<bool> is_buffers_setup{
false};
305 RingBuffer<uint8_t> buffer_out{0};
306 RingBuffer<uint8_t> buffer_in{0};
307 std::mutex write_mtx;
309 std::atomic<int> buffer_size{0};
316 void setupBuffers(
int size = MA_BUFFER_SIZE) {
317 std::lock_guard<std::mutex> guard(write_mtx);
318 if (is_buffers_setup.load())
return;
321 if (size <= 0 || size > 1024 * 1024) {
322 LOGE(
"Invalid buffer size: %d", size);
326 buffer_size.store(size);
327 int buffer_count = config.buffer_count;
330 size_t total_size =
static_cast<size_t>(size) * buffer_count;
331 if (total_size > 100 * 1024 * 1024) {
332 LOGE(
"Buffer size too large: %zu bytes", total_size);
336 LOGI(
"setupBuffers: %d * %d = %zu bytes", size, buffer_count, total_size);
338 if (buffer_out.
size() == 0 && config.is_output) {
339 if (!buffer_out.
resize(size * buffer_count)) {
340 LOGE(
"Failed to resize output buffer");
344 if (buffer_in.
size() == 0 && config.is_input) {
345 if (!buffer_in.
resize(size * buffer_count)) {
346 LOGE(
"Failed to resize input buffer");
350 is_buffers_setup.store(
true);
355 delay(config.delay_ms_if_buffer_full);
359 static void data_callback(ma_device *pDevice,
void *pOutput,
360 const void *pInput, ma_uint32 frameCount) {
361 MiniAudioStream *self = (MiniAudioStream *)pDevice->pUserData;
362 if (!self || !self->is_active.load()) {
366 AudioInfo cfg = self->audioInfo();
367 if (cfg.channels == 0 || cfg.bits_per_sample == 0) {
368 LOGE(
"Invalid audio configuration in callback");
372 int bytes = frameCount * cfg.channels * cfg.bits_per_sample / 8;
373 if (bytes <= 0 || bytes > 1024 * 1024) {
374 LOGE(
"Invalid byte count in callback: %d", bytes);
378 self->setupBuffers(bytes);
380 if (pInput && self->buffer_in.size() > 0) {
384 const int max_retries = 100;
386 while (open > 0 && retry_count < max_retries && self->is_active.load()) {
389 std::unique_lock<std::mutex> guard(self->read_mtx);
390 len = self->buffer_in.writeArray((uint8_t *)pInput + processed, open);
404 memset(pOutput, 0, bytes);
405 if (self->is_playing.load() && self->buffer_out.size() > 0) {
408 int consecutive_failures = 0;
409 const int max_failures = self->config.underrun_tolerance;
411 while (open > 0 && self->is_active.load()) {
414 std::lock_guard<std::mutex> guard(self->write_mtx);
415 len = self->buffer_out.readArray((uint8_t *)pOutput + processed, open);
421 consecutive_failures++;
423 if (consecutive_failures >= max_failures && self->config.auto_restart_on_underrun) {
424 LOGW(
"Buffer underrun detected, stopping playback");
425 self->is_playing.store(
false);
431 consecutive_failures = 0;