Atlas - SDL_audioqueue.c

Home / ext / SDL / src / audio Lines: 1 | Size: 17416 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 Sam Lantinga <[email protected]> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21#include "SDL_internal.h" 22 23#include "SDL_audioqueue.h" 24#include "SDL_sysaudio.h" 25 26typedef struct SDL_MemoryPool SDL_MemoryPool; 27 28struct SDL_MemoryPool 29{ 30 void *free_blocks; 31 size_t block_size; 32 size_t num_free; 33 size_t max_free; 34}; 35 36struct SDL_AudioTrack 37{ 38 SDL_AudioSpec spec; 39 int *chmap; 40 bool flushed; 41 SDL_AudioTrack *next; 42 43 void *userdata; 44 SDL_ReleaseAudioBufferCallback callback; 45 46 Uint8 *data; 47 size_t head; 48 size_t tail; 49 size_t capacity; 50 51 int chmap_storage[SDL_MAX_CHANNELMAP_CHANNELS]; // !!! FIXME: this needs to grow if SDL ever supports more channels. But if it grows, we should probably be more clever about allocations. 52}; 53 54struct SDL_AudioQueue 55{ 56 SDL_AudioTrack *head; 57 SDL_AudioTrack *tail; 58 59 Uint8 *history_buffer; 60 size_t history_length; 61 size_t history_capacity; 62 63 SDL_MemoryPool track_pool; 64 SDL_MemoryPool chunk_pool; 65}; 66 67// Allocate a new block, avoiding checking for ones already in the pool 68static void *AllocNewMemoryPoolBlock(const SDL_MemoryPool *pool) 69{ 70 return SDL_malloc(pool->block_size); 71} 72 73// Allocate a new block, first checking if there are any in the pool 74static void *AllocMemoryPoolBlock(SDL_MemoryPool *pool) 75{ 76 if (pool->num_free == 0) { 77 return AllocNewMemoryPoolBlock(pool); 78 } 79 80 void *block = pool->free_blocks; 81 pool->free_blocks = *(void **)block; 82 --pool->num_free; 83 return block; 84} 85 86// Free a block, or add it to the pool if there's room 87static void FreeMemoryPoolBlock(SDL_MemoryPool *pool, void *block) 88{ 89 if (pool->num_free < pool->max_free) { 90 *(void **)block = pool->free_blocks; 91 pool->free_blocks = block; 92 ++pool->num_free; 93 } else { 94 SDL_free(block); 95 } 96} 97 98// Destroy a pool and all of its blocks 99static void DestroyMemoryPool(SDL_MemoryPool *pool) 100{ 101 void *block = pool->free_blocks; 102 pool->free_blocks = NULL; 103 pool->num_free = 0; 104 105 while (block) { 106 void *next = *(void **)block; 107 SDL_free(block); 108 block = next; 109 } 110} 111 112// Keeping a list of free chunks reduces memory allocations, 113// But also increases the amount of work to perform when freeing the track. 114static void InitMemoryPool(SDL_MemoryPool *pool, size_t block_size, size_t max_free) 115{ 116 SDL_zerop(pool); 117 118 SDL_assert(block_size >= sizeof(void *)); 119 pool->block_size = block_size; 120 pool->max_free = max_free; 121} 122 123// Allocates a number of blocks and adds them to the pool 124static bool ReserveMemoryPoolBlocks(SDL_MemoryPool *pool, size_t num_blocks) 125{ 126 for (; num_blocks; --num_blocks) { 127 void *block = AllocNewMemoryPoolBlock(pool); 128 129 if (block == NULL) { 130 return false; 131 } 132 133 *(void **)block = pool->free_blocks; 134 pool->free_blocks = block; 135 ++pool->num_free; 136 } 137 138 return true; 139} 140 141void SDL_DestroyAudioQueue(SDL_AudioQueue *queue) 142{ 143 SDL_ClearAudioQueue(queue); 144 145 DestroyMemoryPool(&queue->track_pool); 146 DestroyMemoryPool(&queue->chunk_pool); 147 SDL_aligned_free(queue->history_buffer); 148 149 SDL_free(queue); 150} 151 152SDL_AudioQueue *SDL_CreateAudioQueue(size_t chunk_size) 153{ 154 SDL_AudioQueue *queue = (SDL_AudioQueue *)SDL_calloc(1, sizeof(*queue)); 155 156 if (!queue) { 157 return NULL; 158 } 159 160 InitMemoryPool(&queue->track_pool, sizeof(SDL_AudioTrack), 8); 161 InitMemoryPool(&queue->chunk_pool, chunk_size, 4); 162 163 if (!ReserveMemoryPoolBlocks(&queue->track_pool, 2)) { 164 SDL_DestroyAudioQueue(queue); 165 return NULL; 166 } 167 168 return queue; 169} 170 171static void DestroyAudioTrack(SDL_AudioQueue *queue, SDL_AudioTrack *track) 172{ 173 track->callback(track->userdata, track->data, (int)track->capacity); 174 175 FreeMemoryPoolBlock(&queue->track_pool, track); 176} 177 178void SDL_ClearAudioQueue(SDL_AudioQueue *queue) 179{ 180 SDL_AudioTrack *track = queue->head; 181 182 queue->head = NULL; 183 queue->tail = NULL; 184 queue->history_length = 0; 185 186 while (track) { 187 SDL_AudioTrack *next = track->next; 188 DestroyAudioTrack(queue, track); 189 track = next; 190 } 191} 192 193static void FlushAudioTrack(SDL_AudioTrack *track) 194{ 195 track->flushed = true; 196} 197 198void SDL_FlushAudioQueue(SDL_AudioQueue *queue) 199{ 200 SDL_AudioTrack *track = queue->tail; 201 202 if (track) { 203 FlushAudioTrack(track); 204 } 205} 206 207void SDL_PopAudioQueueHead(SDL_AudioQueue *queue) 208{ 209 SDL_AudioTrack *track = queue->head; 210 211 for (;;) { 212 bool flushed = track->flushed; 213 214 SDL_AudioTrack *next = track->next; 215 DestroyAudioTrack(queue, track); 216 track = next; 217 218 if (flushed) { 219 break; 220 } 221 } 222 223 queue->head = track; 224 queue->history_length = 0; 225 226 if (!track) { 227 queue->tail = NULL; 228 } 229} 230 231SDL_AudioTrack *SDL_CreateAudioTrack( 232 SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const int *chmap, 233 Uint8 *data, size_t len, size_t capacity, 234 SDL_ReleaseAudioBufferCallback callback, void *userdata) 235{ 236 SDL_AudioTrack *track = (SDL_AudioTrack *)AllocMemoryPoolBlock(&queue->track_pool); 237 238 if (!track) { 239 return NULL; 240 } 241 242 SDL_zerop(track); 243 244 if (chmap) { 245 SDL_assert(SDL_arraysize(track->chmap_storage) >= spec->channels); 246 SDL_memcpy(track->chmap_storage, chmap, sizeof (*chmap) * spec->channels); 247 track->chmap = track->chmap_storage; 248 } 249 250 SDL_copyp(&track->spec, spec); 251 252 track->userdata = userdata; 253 track->callback = callback; 254 track->data = data; 255 track->head = 0; 256 track->tail = len; 257 track->capacity = capacity; 258 259 return track; 260} 261 262static void SDLCALL FreeChunkedAudioBuffer(void *userdata, const void *buf, int len) 263{ 264 SDL_AudioQueue *queue = (SDL_AudioQueue *)userdata; 265 266 FreeMemoryPoolBlock(&queue->chunk_pool, (void *)buf); 267} 268 269static SDL_AudioTrack *CreateChunkedAudioTrack(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const int *chmap) 270{ 271 Uint8 *chunk = (Uint8 *)AllocMemoryPoolBlock(&queue->chunk_pool); 272 273 if (!chunk) { 274 return NULL; 275 } 276 277 size_t capacity = queue->chunk_pool.block_size; 278 capacity -= capacity % SDL_AUDIO_FRAMESIZE(*spec); 279 280 SDL_AudioTrack *track = SDL_CreateAudioTrack(queue, spec, chmap, chunk, 0, capacity, FreeChunkedAudioBuffer, queue); 281 282 if (!track) { 283 FreeMemoryPoolBlock(&queue->chunk_pool, chunk); 284 return NULL; 285 } 286 287 return track; 288} 289 290void SDL_AddTrackToAudioQueue(SDL_AudioQueue *queue, SDL_AudioTrack *track) 291{ 292 SDL_AudioTrack *tail = queue->tail; 293 294 if (tail) { 295 // If the spec has changed, make sure to flush the previous track 296 if (!SDL_AudioSpecsEqual(&tail->spec, &track->spec, tail->chmap, track->chmap)) { 297 FlushAudioTrack(tail); 298 } 299 300 tail->next = track; 301 } else { 302 queue->head = track; 303 } 304 305 queue->tail = track; 306} 307 308static size_t WriteToAudioTrack(SDL_AudioTrack *track, const Uint8 *data, size_t len) 309{ 310 if (track->flushed || track->tail >= track->capacity) { 311 return 0; 312 } 313 314 len = SDL_min(len, track->capacity - track->tail); 315 SDL_memcpy(&track->data[track->tail], data, len); 316 track->tail += len; 317 318 return len; 319} 320 321bool SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const int *chmap, const Uint8 *data, size_t len) 322{ 323 if (len == 0) { 324 return true; 325 } 326 327 SDL_AudioTrack *track = queue->tail; 328 329 if (track) { 330 if (!SDL_AudioSpecsEqual(&track->spec, spec, track->chmap, chmap)) { 331 FlushAudioTrack(track); 332 } 333 } else { 334 SDL_assert(!queue->head); 335 track = CreateChunkedAudioTrack(queue, spec, chmap); 336 337 if (!track) { 338 return false; 339 } 340 341 queue->head = track; 342 queue->tail = track; 343 } 344 345 for (;;) { 346 const size_t written = WriteToAudioTrack(track, data, len); 347 data += written; 348 len -= written; 349 350 if (len == 0) { 351 break; 352 } 353 354 SDL_AudioTrack *new_track = CreateChunkedAudioTrack(queue, spec, chmap); 355 356 if (!new_track) { 357 return false; 358 } 359 360 track->next = new_track; 361 queue->tail = new_track; 362 track = new_track; 363 } 364 365 return true; 366} 367 368void *SDL_BeginAudioQueueIter(SDL_AudioQueue *queue) 369{ 370 return queue->head; 371} 372 373size_t SDL_NextAudioQueueIter(SDL_AudioQueue *queue, void **inout_iter, SDL_AudioSpec *out_spec, int **out_chmap, bool *out_flushed) 374{ 375 SDL_AudioTrack *iter = (SDL_AudioTrack *)(*inout_iter); 376 SDL_assert(iter != NULL); 377 378 SDL_copyp(out_spec, &iter->spec); 379 *out_chmap = iter->chmap; 380 381 bool flushed = false; 382 size_t queued_bytes = 0; 383 384 while (iter) { 385 SDL_AudioTrack *track = iter; 386 iter = iter->next; 387 388 size_t avail = track->tail - track->head; 389 390 if (avail >= SDL_SIZE_MAX - queued_bytes) { 391 queued_bytes = SDL_SIZE_MAX; 392 flushed = false; 393 break; 394 } 395 396 queued_bytes += avail; 397 flushed = track->flushed; 398 399 if (flushed) { 400 break; 401 } 402 } 403 404 *inout_iter = iter; 405 *out_flushed = flushed; 406 407 return queued_bytes; 408} 409 410static const Uint8 *PeekIntoAudioQueuePast(SDL_AudioQueue *queue, Uint8 *data, size_t len) 411{ 412 SDL_AudioTrack *track = queue->head; 413 414 if (track->head >= len) { 415 return &track->data[track->head - len]; 416 } 417 418 size_t past = len - track->head; 419 420 if (past > queue->history_length) { 421 return NULL; 422 } 423 424 SDL_memcpy(data, &queue->history_buffer[queue->history_length - past], past); 425 SDL_memcpy(&data[past], track->data, track->head); 426 427 return data; 428} 429 430static void UpdateAudioQueueHistory(SDL_AudioQueue *queue, 431 const Uint8 *data, size_t len) 432{ 433 Uint8 *history_buffer = queue->history_buffer; 434 size_t history_bytes = queue->history_length; 435 436 if (len >= history_bytes) { 437 SDL_memcpy(history_buffer, &data[len - history_bytes], history_bytes); 438 } else { 439 size_t preserve = history_bytes - len; 440 SDL_memmove(history_buffer, &history_buffer[len], preserve); 441 SDL_memcpy(&history_buffer[preserve], data, len); 442 } 443} 444 445static const Uint8 *ReadFromAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len) 446{ 447 SDL_AudioTrack *track = queue->head; 448 449 if (track->tail - track->head >= len) { 450 const Uint8 *ptr = &track->data[track->head]; 451 track->head += len; 452 return ptr; 453 } 454 455 size_t total = 0; 456 457 for (;;) { 458 size_t avail = SDL_min(len - total, track->tail - track->head); 459 SDL_memcpy(&data[total], &track->data[track->head], avail); 460 track->head += avail; 461 total += avail; 462 463 if (total == len) { 464 break; 465 } 466 467 if (track->flushed) { 468 SDL_SetError("Reading past end of flushed track"); 469 return NULL; 470 } 471 472 SDL_AudioTrack *next = track->next; 473 474 if (!next) { 475 SDL_SetError("Reading past end of incomplete track"); 476 return NULL; 477 } 478 479 UpdateAudioQueueHistory(queue, track->data, track->tail); 480 481 queue->head = next; 482 DestroyAudioTrack(queue, track); 483 track = next; 484 } 485 486 return data; 487} 488 489static const Uint8 *PeekIntoAudioQueueFuture(SDL_AudioQueue *queue, Uint8 *data, size_t len) 490{ 491 SDL_AudioTrack *track = queue->head; 492 493 if (track->tail - track->head >= len) { 494 return &track->data[track->head]; 495 } 496 497 size_t total = 0; 498 499 for (;;) { 500 size_t avail = SDL_min(len - total, track->tail - track->head); 501 SDL_memcpy(&data[total], &track->data[track->head], avail); 502 total += avail; 503 504 if (total == len) { 505 break; 506 } 507 508 if (track->flushed) { 509 // If we have run out of data, fill the rest with silence. 510 SDL_memset(&data[total], SDL_GetSilenceValueForFormat(track->spec.format), len - total); 511 break; 512 } 513 514 track = track->next; 515 516 if (!track) { 517 SDL_SetError("Peeking past end of incomplete track"); 518 return NULL; 519 } 520 } 521 522 return data; 523} 524 525const Uint8 *SDL_ReadFromAudioQueue(SDL_AudioQueue *queue, 526 Uint8 *dst, SDL_AudioFormat dst_format, int dst_channels, const int *dst_map, 527 int past_frames, int present_frames, int future_frames, 528 Uint8 *scratch, float gain) 529{ 530 SDL_AudioTrack *track = queue->head; 531 532 if (!track) { 533 return NULL; 534 } 535 536 SDL_AudioFormat src_format = track->spec.format; 537 int src_channels = track->spec.channels; 538 const int *src_map = track->chmap; 539 540 size_t src_frame_size = SDL_AUDIO_BYTESIZE(src_format) * src_channels; 541 size_t dst_frame_size = SDL_AUDIO_BYTESIZE(dst_format) * dst_channels; 542 543 size_t src_past_bytes = past_frames * src_frame_size; 544 size_t src_present_bytes = present_frames * src_frame_size; 545 size_t src_future_bytes = future_frames * src_frame_size; 546 547 size_t dst_past_bytes = past_frames * dst_frame_size; 548 size_t dst_present_bytes = present_frames * dst_frame_size; 549 size_t dst_future_bytes = future_frames * dst_frame_size; 550 551 const bool convert = (src_format != dst_format) || (src_channels != dst_channels) || (gain != 1.0f); 552 553 if (convert && !dst) { 554 // The user didn't ask for the data to be copied, but we need to convert it, so store it in the scratch buffer 555 dst = scratch; 556 } 557 558 // Can we get all of the data straight from this track? 559 if ((track->head >= src_past_bytes) && ((track->tail - track->head) >= (src_present_bytes + src_future_bytes))) { 560 const Uint8 *ptr = &track->data[track->head - src_past_bytes]; 561 track->head += src_present_bytes; 562 563 // Do we still need to copy/convert the data? 564 if (dst) { 565 ConvertAudio(past_frames + present_frames + future_frames, ptr, 566 src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch, gain); 567 ptr = dst; 568 } 569 570 return ptr; 571 } 572 573 if (!dst) { 574 // The user didn't ask for the data to be copied, but we need to, so store it in the scratch buffer 575 dst = scratch; 576 } else if (!convert) { 577 // We are only copying, not converting, so copy straight into the dst buffer 578 scratch = dst; 579 } 580 581 Uint8 *ptr = dst; 582 583 if (src_past_bytes) { 584 ConvertAudio(past_frames, PeekIntoAudioQueuePast(queue, scratch, src_past_bytes), src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch, gain); 585 dst += dst_past_bytes; 586 scratch += dst_past_bytes; 587 } 588 589 if (src_present_bytes) { 590 ConvertAudio(present_frames, ReadFromAudioQueue(queue, scratch, src_present_bytes), src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch, gain); 591 dst += dst_present_bytes; 592 scratch += dst_present_bytes; 593 } 594 595 if (src_future_bytes) { 596 ConvertAudio(future_frames, PeekIntoAudioQueueFuture(queue, scratch, src_future_bytes), src_format, src_channels, src_map, dst, dst_format, dst_channels, dst_map, scratch, gain); 597 dst += dst_future_bytes; 598 scratch += dst_future_bytes; 599 } 600 601 return ptr; 602} 603 604size_t SDL_GetAudioQueueQueued(SDL_AudioQueue *queue) 605{ 606 size_t total = 0; 607 void *iter = SDL_BeginAudioQueueIter(queue); 608 609 while (iter) { 610 SDL_AudioSpec src_spec; 611 int *src_chmap; 612 bool flushed; 613 614 size_t avail = SDL_NextAudioQueueIter(queue, &iter, &src_spec, &src_chmap, &flushed); 615 616 if (avail >= SDL_SIZE_MAX - total) { 617 total = SDL_SIZE_MAX; 618 break; 619 } 620 621 total += avail; 622 } 623 624 return total; 625} 626 627bool SDL_ResetAudioQueueHistory(SDL_AudioQueue *queue, int num_frames) 628{ 629 SDL_AudioTrack *track = queue->head; 630 631 if (!track) { 632 return false; 633 } 634 635 size_t length = num_frames * SDL_AUDIO_FRAMESIZE(track->spec); 636 Uint8 *history_buffer = queue->history_buffer; 637 638 if (queue->history_capacity < length) { 639 history_buffer = (Uint8 *)SDL_aligned_alloc(SDL_GetSIMDAlignment(), length); 640 if (!history_buffer) { 641 return false; 642 } 643 SDL_aligned_free(queue->history_buffer); 644 queue->history_buffer = history_buffer; 645 queue->history_capacity = length; 646 } 647 648 queue->history_length = length; 649 SDL_memset(history_buffer, SDL_GetSilenceValueForFormat(track->spec.format), length); 650 651 return true; 652} 653
[FILE END]
(C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.