1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include <audio_utils/sndfile.h> 18 #include <audio_utils/primitives.h> 19 #ifdef HAVE_STDERR 20 #include <stdio.h> 21 #endif 22 #include <string.h> 23 #include <errno.h> 24 25 #define WAVE_FORMAT_PCM 1 26 #define WAVE_FORMAT_IEEE_FLOAT 3 27 #define WAVE_FORMAT_EXTENSIBLE 0xFFFE 28 29 struct SNDFILE_ { 30 int mode; 31 uint8_t *temp; // realloc buffer used for shrinking 16 bits to 8 bits and byte-swapping 32 FILE *stream; 33 size_t bytesPerFrame; 34 size_t remaining; // frames unread for SFM_READ, frames written for SFM_WRITE 35 SF_INFO info; 36 }; 37 38 static unsigned little2u(unsigned char *ptr) 39 { 40 return (ptr[1] << 8) + ptr[0]; 41 } 42 43 static unsigned little4u(unsigned char *ptr) 44 { 45 return (ptr[3] << 24) + (ptr[2] << 16) + (ptr[1] << 8) + ptr[0]; 46 } 47 48 static int isLittleEndian(void) 49 { 50 static const short one = 1; 51 return *((const char *) &one) == 1; 52 } 53 54 // "swab" conflicts with OS X <string.h> 55 static void my_swab(short *ptr, size_t numToSwap) 56 { 57 while (numToSwap > 0) { 58 *ptr = little2u((unsigned char *) ptr); 59 --numToSwap; 60 ++ptr; 61 } 62 } 63 64 static SNDFILE *sf_open_read(const char *path, SF_INFO *info) 65 { 66 FILE *stream = fopen(path, "rb"); 67 if (stream == NULL) { 68 #ifdef HAVE_STDERR 69 fprintf(stderr, "fopen %s failed errno %d\n", path, errno); 70 #endif 71 return NULL; 72 } 73 74 SNDFILE *handle = (SNDFILE *) malloc(sizeof(SNDFILE)); 75 handle->mode = SFM_READ; 76 handle->temp = NULL; 77 handle->stream = stream; 78 handle->info.format = SF_FORMAT_WAV; 79 80 // don't attempt to parse all valid forms, just the most common ones 81 unsigned char wav[12]; 82 size_t actual; 83 actual = fread(wav, sizeof(char), sizeof(wav), stream); 84 if (actual < 12) { 85 #ifdef HAVE_STDERR 86 fprintf(stderr, "actual %zu < 44\n", actual); 87 #endif 88 goto close; 89 } 90 if (memcmp(wav, "RIFF", 4)) { 91 #ifdef HAVE_STDERR 92 fprintf(stderr, "wav != RIFF\n"); 93 #endif 94 goto close; 95 } 96 unsigned riffSize = little4u(&wav[4]); 97 if (riffSize < 4) { 98 #ifdef HAVE_STDERR 99 fprintf(stderr, "riffSize %u < 4\n", riffSize); 100 #endif 101 goto close; 102 } 103 if (memcmp(&wav[8], "WAVE", 4)) { 104 #ifdef HAVE_STDERR 105 fprintf(stderr, "missing WAVE\n"); 106 #endif 107 goto close; 108 } 109 size_t remaining = riffSize - 4; 110 int hadFmt = 0; 111 int hadData = 0; 112 long dataTell = 0L; 113 while (remaining >= 8) { 114 unsigned char chunk[8]; 115 actual = fread(chunk, sizeof(char), sizeof(chunk), stream); 116 if (actual != sizeof(chunk)) { 117 #ifdef HAVE_STDERR 118 fprintf(stderr, "actual %zu != %zu\n", actual, sizeof(chunk)); 119 #endif 120 goto close; 121 } 122 remaining -= 8; 123 unsigned chunkSize = little4u(&chunk[4]); 124 if (chunkSize > remaining) { 125 #ifdef HAVE_STDERR 126 fprintf(stderr, "chunkSize %u > remaining %zu\n", chunkSize, remaining); 127 #endif 128 goto close; 129 } 130 if (!memcmp(&chunk[0], "fmt ", 4)) { 131 if (hadFmt) { 132 #ifdef HAVE_STDERR 133 fprintf(stderr, "multiple fmt\n"); 134 #endif 135 goto close; 136 } 137 if (chunkSize < 2) { 138 #ifdef HAVE_STDERR 139 fprintf(stderr, "chunkSize %u < 2\n", chunkSize); 140 #endif 141 goto close; 142 } 143 unsigned char fmt[40]; 144 actual = fread(fmt, sizeof(char), 2, stream); 145 if (actual != 2) { 146 #ifdef HAVE_STDERR 147 fprintf(stderr, "actual %zu != 2\n", actual); 148 #endif 149 goto close; 150 } 151 unsigned format = little2u(&fmt[0]); 152 size_t minSize = 0; 153 switch (format) { 154 case WAVE_FORMAT_PCM: 155 case WAVE_FORMAT_IEEE_FLOAT: 156 minSize = 16; 157 break; 158 case WAVE_FORMAT_EXTENSIBLE: 159 minSize = 40; 160 break; 161 default: 162 #ifdef HAVE_STDERR 163 fprintf(stderr, "unsupported format %u\n", format); 164 #endif 165 goto close; 166 } 167 if (chunkSize < minSize) { 168 #ifdef HAVE_STDERR 169 fprintf(stderr, "chunkSize %u < minSize %zu\n", chunkSize, minSize); 170 #endif 171 goto close; 172 } 173 actual = fread(&fmt[2], sizeof(char), minSize - 2, stream); 174 if (actual != minSize - 2) { 175 #ifdef HAVE_STDERR 176 fprintf(stderr, "actual %zu != %zu\n", actual, minSize - 16); 177 #endif 178 goto close; 179 } 180 if (chunkSize > minSize) { 181 fseek(stream, (long) (chunkSize - minSize), SEEK_CUR); 182 } 183 unsigned channels = little2u(&fmt[2]); 184 // FIXME FCC_8 185 if (channels != 1 && channels != 2 && channels != 4 && channels != 6 && channels != 8) { 186 #ifdef HAVE_STDERR 187 fprintf(stderr, "unsupported channels %u\n", channels); 188 #endif 189 goto close; 190 } 191 unsigned samplerate = little4u(&fmt[4]); 192 if (samplerate == 0) { 193 #ifdef HAVE_STDERR 194 fprintf(stderr, "samplerate %u == 0\n", samplerate); 195 #endif 196 goto close; 197 } 198 // ignore byte rate 199 // ignore block alignment 200 unsigned bitsPerSample = little2u(&fmt[14]); 201 if (bitsPerSample != 8 && bitsPerSample != 16 && bitsPerSample != 24 && 202 bitsPerSample != 32) { 203 #ifdef HAVE_STDERR 204 fprintf(stderr, "bitsPerSample %u != 8 or 16 or 24 or 32\n", bitsPerSample); 205 #endif 206 goto close; 207 } 208 unsigned bytesPerFrame = (bitsPerSample >> 3) * channels; 209 handle->bytesPerFrame = bytesPerFrame; 210 handle->info.samplerate = samplerate; 211 handle->info.channels = channels; 212 switch (bitsPerSample) { 213 case 8: 214 handle->info.format |= SF_FORMAT_PCM_U8; 215 break; 216 case 16: 217 handle->info.format |= SF_FORMAT_PCM_16; 218 break; 219 case 24: 220 handle->info.format |= SF_FORMAT_PCM_24; 221 break; 222 case 32: 223 if (format == WAVE_FORMAT_IEEE_FLOAT) 224 handle->info.format |= SF_FORMAT_FLOAT; 225 else 226 handle->info.format |= SF_FORMAT_PCM_32; 227 break; 228 } 229 hadFmt = 1; 230 } else if (!memcmp(&chunk[0], "data", 4)) { 231 if (!hadFmt) { 232 #ifdef HAVE_STDERR 233 fprintf(stderr, "data not preceded by fmt\n"); 234 #endif 235 goto close; 236 } 237 if (hadData) { 238 #ifdef HAVE_STDERR 239 fprintf(stderr, "multiple data\n"); 240 #endif 241 goto close; 242 } 243 handle->remaining = chunkSize / handle->bytesPerFrame; 244 handle->info.frames = handle->remaining; 245 dataTell = ftell(stream); 246 if (chunkSize > 0) { 247 fseek(stream, (long) chunkSize, SEEK_CUR); 248 } 249 hadData = 1; 250 } else if (!memcmp(&chunk[0], "fact", 4)) { 251 // ignore fact 252 if (chunkSize > 0) { 253 fseek(stream, (long) chunkSize, SEEK_CUR); 254 } 255 } else { 256 // ignore unknown chunk 257 #ifdef HAVE_STDERR 258 fprintf(stderr, "ignoring unknown chunk %c%c%c%c\n", 259 chunk[0], chunk[1], chunk[2], chunk[3]); 260 #endif 261 if (chunkSize > 0) { 262 fseek(stream, (long) chunkSize, SEEK_CUR); 263 } 264 } 265 remaining -= chunkSize; 266 } 267 if (remaining > 0) { 268 #ifdef HAVE_STDERR 269 fprintf(stderr, "partial chunk at end of RIFF, remaining %zu\n", remaining); 270 #endif 271 goto close; 272 } 273 if (!hadData) { 274 #ifdef HAVE_STDERR 275 fprintf(stderr, "missing data\n"); 276 #endif 277 goto close; 278 } 279 (void) fseek(stream, dataTell, SEEK_SET); 280 *info = handle->info; 281 return handle; 282 283 close: 284 free(handle); 285 fclose(stream); 286 return NULL; 287 } 288 289 static void write4u(unsigned char *ptr, unsigned u) 290 { 291 ptr[0] = u; 292 ptr[1] = u >> 8; 293 ptr[2] = u >> 16; 294 ptr[3] = u >> 24; 295 } 296 297 static SNDFILE *sf_open_write(const char *path, SF_INFO *info) 298 { 299 int sub = info->format & SF_FORMAT_SUBMASK; 300 if (!( 301 (info->samplerate > 0) && 302 // FIXME FCC_8 303 (info->channels > 0 && info->channels <= 8) && 304 ((info->format & SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV) && 305 (sub == SF_FORMAT_PCM_16 || sub == SF_FORMAT_PCM_U8 || sub == SF_FORMAT_FLOAT || 306 sub == SF_FORMAT_PCM_24 || sub == SF_FORMAT_PCM_32) 307 )) { 308 return NULL; 309 } 310 FILE *stream = fopen(path, "w+b"); 311 if (stream == NULL) { 312 #ifdef HAVE_STDERR 313 fprintf(stderr, "fopen %s failed errno %d\n", path, errno); 314 #endif 315 return NULL; 316 } 317 unsigned char wav[58]; 318 memset(wav, 0, sizeof(wav)); 319 memcpy(wav, "RIFF", 4); 320 memcpy(&wav[8], "WAVEfmt ", 8); 321 if (sub == SF_FORMAT_FLOAT) { 322 wav[4] = 50; // riffSize 323 wav[16] = 18; // fmtSize 324 wav[20] = WAVE_FORMAT_IEEE_FLOAT; 325 } else { 326 wav[4] = 36; // riffSize 327 wav[16] = 16; // fmtSize 328 wav[20] = WAVE_FORMAT_PCM; 329 } 330 wav[22] = info->channels; 331 write4u(&wav[24], info->samplerate); 332 unsigned bitsPerSample; 333 switch (sub) { 334 case SF_FORMAT_PCM_16: 335 bitsPerSample = 16; 336 break; 337 case SF_FORMAT_PCM_U8: 338 bitsPerSample = 8; 339 break; 340 case SF_FORMAT_FLOAT: 341 bitsPerSample = 32; 342 break; 343 case SF_FORMAT_PCM_24: 344 bitsPerSample = 24; 345 break; 346 case SF_FORMAT_PCM_32: 347 bitsPerSample = 32; 348 break; 349 default: // not reachable 350 bitsPerSample = 0; 351 break; 352 } 353 unsigned blockAlignment = (bitsPerSample >> 3) * info->channels; 354 unsigned byteRate = info->samplerate * blockAlignment; 355 write4u(&wav[28], byteRate); 356 wav[32] = blockAlignment; 357 wav[34] = bitsPerSample; 358 size_t extra = 0; 359 if (sub == SF_FORMAT_FLOAT) { 360 memcpy(&wav[38], "fact", 4); 361 wav[42] = 4; 362 memcpy(&wav[50], "data", 4); 363 extra = 14; 364 } else 365 memcpy(&wav[36], "data", 4); 366 // dataSize is initially zero 367 (void) fwrite(wav, 44 + extra, 1, stream); 368 SNDFILE *handle = (SNDFILE *) malloc(sizeof(SNDFILE)); 369 handle->mode = SFM_WRITE; 370 handle->temp = NULL; 371 handle->stream = stream; 372 handle->bytesPerFrame = blockAlignment; 373 handle->remaining = 0; 374 handle->info = *info; 375 return handle; 376 } 377 378 SNDFILE *sf_open(const char *path, int mode, SF_INFO *info) 379 { 380 if (path == NULL || info == NULL) { 381 #ifdef HAVE_STDERR 382 fprintf(stderr, "path=%p info=%p\n", path, info); 383 #endif 384 return NULL; 385 } 386 switch (mode) { 387 case SFM_READ: 388 return sf_open_read(path, info); 389 case SFM_WRITE: 390 return sf_open_write(path, info); 391 default: 392 #ifdef HAVE_STDERR 393 fprintf(stderr, "mode=%d\n", mode); 394 #endif 395 return NULL; 396 } 397 } 398 399 void sf_close(SNDFILE *handle) 400 { 401 if (handle == NULL) 402 return; 403 free(handle->temp); 404 if (handle->mode == SFM_WRITE) { 405 (void) fflush(handle->stream); 406 rewind(handle->stream); 407 unsigned char wav[58]; 408 size_t extra = (handle->info.format & SF_FORMAT_SUBMASK) == SF_FORMAT_FLOAT ? 14 : 0; 409 (void) fread(wav, 44 + extra, 1, handle->stream); 410 unsigned dataSize = handle->remaining * handle->bytesPerFrame; 411 write4u(&wav[4], dataSize + 36 + extra); // riffSize 412 write4u(&wav[40 + extra], dataSize); // dataSize 413 rewind(handle->stream); 414 (void) fwrite(wav, 44 + extra, 1, handle->stream); 415 } 416 (void) fclose(handle->stream); 417 free(handle); 418 } 419 420 sf_count_t sf_readf_short(SNDFILE *handle, short *ptr, sf_count_t desiredFrames) 421 { 422 if (handle == NULL || handle->mode != SFM_READ || ptr == NULL || !handle->remaining || 423 desiredFrames <= 0) { 424 return 0; 425 } 426 if (handle->remaining < (size_t) desiredFrames) { 427 desiredFrames = handle->remaining; 428 } 429 // does not check for numeric overflow 430 size_t desiredBytes = desiredFrames * handle->bytesPerFrame; 431 size_t actualBytes; 432 void *temp = NULL; 433 unsigned format = handle->info.format & SF_FORMAT_SUBMASK; 434 if (format == SF_FORMAT_PCM_32 || format == SF_FORMAT_FLOAT || format == SF_FORMAT_PCM_24) { 435 temp = malloc(desiredBytes); 436 actualBytes = fread(temp, sizeof(char), desiredBytes, handle->stream); 437 } else { 438 actualBytes = fread(ptr, sizeof(char), desiredBytes, handle->stream); 439 } 440 size_t actualFrames = actualBytes / handle->bytesPerFrame; 441 handle->remaining -= actualFrames; 442 switch (format) { 443 case SF_FORMAT_PCM_U8: 444 memcpy_to_i16_from_u8(ptr, (unsigned char *) ptr, actualFrames * handle->info.channels); 445 break; 446 case SF_FORMAT_PCM_16: 447 if (!isLittleEndian()) 448 my_swab(ptr, actualFrames * handle->info.channels); 449 break; 450 case SF_FORMAT_PCM_32: 451 memcpy_to_i16_from_i32(ptr, (const int *) temp, actualFrames * handle->info.channels); 452 free(temp); 453 break; 454 case SF_FORMAT_FLOAT: 455 memcpy_to_i16_from_float(ptr, (const float *) temp, actualFrames * handle->info.channels); 456 free(temp); 457 break; 458 case SF_FORMAT_PCM_24: 459 memcpy_to_i16_from_p24(ptr, (const uint8_t *) temp, actualFrames * handle->info.channels); 460 free(temp); 461 break; 462 default: 463 memset(ptr, 0, actualFrames * handle->info.channels * sizeof(short)); 464 break; 465 } 466 return actualFrames; 467 } 468 469 sf_count_t sf_readf_float(SNDFILE *handle, float *ptr, sf_count_t desiredFrames) 470 { 471 if (handle == NULL || handle->mode != SFM_READ || ptr == NULL || !handle->remaining || 472 desiredFrames <= 0) { 473 return 0; 474 } 475 if (handle->remaining < (size_t) desiredFrames) { 476 desiredFrames = handle->remaining; 477 } 478 // does not check for numeric overflow 479 size_t desiredBytes = desiredFrames * handle->bytesPerFrame; 480 size_t actualBytes; 481 void *temp = NULL; 482 unsigned format = handle->info.format & SF_FORMAT_SUBMASK; 483 if (format == SF_FORMAT_PCM_16 || format == SF_FORMAT_PCM_U8 || format == SF_FORMAT_PCM_24) { 484 temp = malloc(desiredBytes); 485 actualBytes = fread(temp, sizeof(char), desiredBytes, handle->stream); 486 } else { 487 actualBytes = fread(ptr, sizeof(char), desiredBytes, handle->stream); 488 } 489 size_t actualFrames = actualBytes / handle->bytesPerFrame; 490 handle->remaining -= actualFrames; 491 switch (format) { 492 case SF_FORMAT_PCM_U8: 493 #if 0 494 // TODO - implement 495 memcpy_to_float_from_u8(ptr, (const unsigned char *) temp, 496 actualFrames * handle->info.channels); 497 #endif 498 free(temp); 499 break; 500 case SF_FORMAT_PCM_16: 501 memcpy_to_float_from_i16(ptr, (const short *) temp, actualFrames * handle->info.channels); 502 free(temp); 503 break; 504 case SF_FORMAT_PCM_32: 505 memcpy_to_float_from_i32(ptr, (const int *) ptr, actualFrames * handle->info.channels); 506 break; 507 case SF_FORMAT_FLOAT: 508 break; 509 case SF_FORMAT_PCM_24: 510 memcpy_to_float_from_p24(ptr, (const uint8_t *) temp, actualFrames * handle->info.channels); 511 free(temp); 512 break; 513 default: 514 memset(ptr, 0, actualFrames * handle->info.channels * sizeof(float)); 515 break; 516 } 517 return actualFrames; 518 } 519 520 sf_count_t sf_readf_int(SNDFILE *handle, int *ptr, sf_count_t desiredFrames) 521 { 522 if (handle == NULL || handle->mode != SFM_READ || ptr == NULL || !handle->remaining || 523 desiredFrames <= 0) { 524 return 0; 525 } 526 if (handle->remaining < (size_t) desiredFrames) { 527 desiredFrames = handle->remaining; 528 } 529 // does not check for numeric overflow 530 size_t desiredBytes = desiredFrames * handle->bytesPerFrame; 531 void *temp = NULL; 532 unsigned format = handle->info.format & SF_FORMAT_SUBMASK; 533 size_t actualBytes; 534 if (format == SF_FORMAT_PCM_16 || format == SF_FORMAT_PCM_U8 || format == SF_FORMAT_PCM_24) { 535 temp = malloc(desiredBytes); 536 actualBytes = fread(temp, sizeof(char), desiredBytes, handle->stream); 537 } else { 538 actualBytes = fread(ptr, sizeof(char), desiredBytes, handle->stream); 539 } 540 size_t actualFrames = actualBytes / handle->bytesPerFrame; 541 handle->remaining -= actualFrames; 542 switch (format) { 543 case SF_FORMAT_PCM_U8: 544 #if 0 545 // TODO - implement 546 memcpy_to_i32_from_u8(ptr, (const unsigned char *) temp, 547 actualFrames * handle->info.channels); 548 #endif 549 free(temp); 550 break; 551 case SF_FORMAT_PCM_16: 552 memcpy_to_i32_from_i16(ptr, (const short *) temp, actualFrames * handle->info.channels); 553 free(temp); 554 break; 555 case SF_FORMAT_PCM_32: 556 break; 557 case SF_FORMAT_FLOAT: 558 memcpy_to_i32_from_float(ptr, (const float *) ptr, actualFrames * handle->info.channels); 559 break; 560 case SF_FORMAT_PCM_24: 561 memcpy_to_i32_from_p24(ptr, (const uint8_t *) temp, actualFrames * handle->info.channels); 562 free(temp); 563 break; 564 default: 565 memset(ptr, 0, actualFrames * handle->info.channels * sizeof(int)); 566 break; 567 } 568 return actualFrames; 569 } 570 571 sf_count_t sf_writef_short(SNDFILE *handle, const short *ptr, sf_count_t desiredFrames) 572 { 573 if (handle == NULL || handle->mode != SFM_WRITE || ptr == NULL || desiredFrames <= 0) 574 return 0; 575 size_t desiredBytes = desiredFrames * handle->bytesPerFrame; 576 size_t actualBytes = 0; 577 switch (handle->info.format & SF_FORMAT_SUBMASK) { 578 case SF_FORMAT_PCM_U8: 579 handle->temp = realloc(handle->temp, desiredBytes); 580 memcpy_to_u8_from_i16(handle->temp, ptr, desiredBytes); 581 actualBytes = fwrite(handle->temp, sizeof(char), desiredBytes, handle->stream); 582 break; 583 case SF_FORMAT_PCM_16: 584 // does not check for numeric overflow 585 if (isLittleEndian()) { 586 actualBytes = fwrite(ptr, sizeof(char), desiredBytes, handle->stream); 587 } else { 588 handle->temp = realloc(handle->temp, desiredBytes); 589 memcpy(handle->temp, ptr, desiredBytes); 590 my_swab((short *) handle->temp, desiredFrames * handle->info.channels); 591 actualBytes = fwrite(handle->temp, sizeof(char), desiredBytes, handle->stream); 592 } 593 break; 594 case SF_FORMAT_FLOAT: 595 handle->temp = realloc(handle->temp, desiredBytes); 596 memcpy_to_float_from_i16((float *) handle->temp, ptr, 597 desiredFrames * handle->info.channels); 598 actualBytes = fwrite(handle->temp, sizeof(char), desiredBytes, handle->stream); 599 break; 600 default: 601 break; 602 } 603 size_t actualFrames = actualBytes / handle->bytesPerFrame; 604 handle->remaining += actualFrames; 605 return actualFrames; 606 } 607 608 sf_count_t sf_writef_float(SNDFILE *handle, const float *ptr, sf_count_t desiredFrames) 609 { 610 if (handle == NULL || handle->mode != SFM_WRITE || ptr == NULL || desiredFrames <= 0) 611 return 0; 612 size_t desiredBytes = desiredFrames * handle->bytesPerFrame; 613 size_t actualBytes = 0; 614 switch (handle->info.format & SF_FORMAT_SUBMASK) { 615 case SF_FORMAT_FLOAT: 616 actualBytes = fwrite(ptr, sizeof(char), desiredBytes, handle->stream); 617 break; 618 case SF_FORMAT_PCM_16: 619 handle->temp = realloc(handle->temp, desiredBytes); 620 memcpy_to_i16_from_float((short *) handle->temp, ptr, 621 desiredFrames * handle->info.channels); 622 actualBytes = fwrite(handle->temp, sizeof(char), desiredBytes, handle->stream); 623 break; 624 case SF_FORMAT_PCM_U8: // transcoding from float to byte not yet implemented 625 default: 626 break; 627 } 628 size_t actualFrames = actualBytes / handle->bytesPerFrame; 629 handle->remaining += actualFrames; 630 return actualFrames; 631 } 632 633 sf_count_t sf_writef_int(SNDFILE *handle, const int *ptr, sf_count_t desiredFrames) 634 { 635 if (handle == NULL || handle->mode != SFM_WRITE || ptr == NULL || desiredFrames <= 0) 636 return 0; 637 size_t desiredBytes = desiredFrames * handle->bytesPerFrame; 638 size_t actualBytes = 0; 639 switch (handle->info.format & SF_FORMAT_SUBMASK) { 640 case SF_FORMAT_PCM_32: 641 actualBytes = fwrite(ptr, sizeof(char), desiredBytes, handle->stream); 642 break; 643 default: // transcoding from other formats not yet implemented 644 break; 645 } 646 size_t actualFrames = actualBytes / handle->bytesPerFrame; 647 handle->remaining += actualFrames; 648 return actualFrames; 649 } 650