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 <system/audio.h> 18 #include <audio_utils/sndfile.h> 19 #include <audio_utils/primitives.h> 20 #ifdef HAVE_STDERR 21 #include <stdio.h> 22 #endif 23 #include <string.h> 24 #include <errno.h> 25 26 #define WAVE_FORMAT_PCM 1 27 #define WAVE_FORMAT_IEEE_FLOAT 3 28 #define WAVE_FORMAT_EXTENSIBLE 0xFFFE 29 30 struct SNDFILE_ { 31 int mode; 32 uint8_t *temp; // realloc buffer used for shrinking 16 bits to 8 bits and byte-swapping 33 FILE *stream; 34 size_t bytesPerFrame; 35 size_t remaining; // frames unread for SFM_READ, frames written for SFM_WRITE 36 SF_INFO info; 37 }; 38 39 static unsigned little2u(unsigned char *ptr) 40 { 41 return (ptr[1] << 8) + ptr[0]; 42 } 43 44 static unsigned little4u(unsigned char *ptr) 45 { 46 return (ptr[3] << 24) + (ptr[2] << 16) + (ptr[1] << 8) + ptr[0]; 47 } 48 49 static int isLittleEndian(void) 50 { 51 static const short one = 1; 52 return *((const char *) &one) == 1; 53 } 54 55 // "swab" conflicts with OS X <string.h> 56 static void my_swab(short *ptr, size_t numToSwap) 57 { 58 while (numToSwap > 0) { 59 *ptr = little2u((unsigned char *) ptr); 60 --numToSwap; 61 ++ptr; 62 } 63 } 64 65 static SNDFILE *sf_open_read(const char *path, SF_INFO *info) 66 { 67 FILE *stream = fopen(path, "rb"); 68 if (stream == NULL) { 69 #ifdef HAVE_STDERR 70 fprintf(stderr, "fopen %s failed errno %d\n", path, errno); 71 #endif 72 return NULL; 73 } 74 75 SNDFILE *handle = (SNDFILE *) malloc(sizeof(SNDFILE)); 76 handle->mode = SFM_READ; 77 handle->temp = NULL; 78 handle->stream = stream; 79 handle->info.format = SF_FORMAT_WAV; 80 81 // don't attempt to parse all valid forms, just the most common ones 82 unsigned char wav[12]; 83 size_t actual; 84 actual = fread(wav, sizeof(char), sizeof(wav), stream); 85 if (actual < 12) { 86 #ifdef HAVE_STDERR 87 fprintf(stderr, "actual %zu < 44\n", actual); 88 #endif 89 goto close; 90 } 91 if (memcmp(wav, "RIFF", 4)) { 92 #ifdef HAVE_STDERR 93 fprintf(stderr, "wav != RIFF\n"); 94 #endif 95 goto close; 96 } 97 unsigned riffSize = little4u(&wav[4]); 98 if (riffSize < 4) { 99 #ifdef HAVE_STDERR 100 fprintf(stderr, "riffSize %u < 4\n", riffSize); 101 #endif 102 goto close; 103 } 104 if (memcmp(&wav[8], "WAVE", 4)) { 105 #ifdef HAVE_STDERR 106 fprintf(stderr, "missing WAVE\n"); 107 #endif 108 goto close; 109 } 110 size_t remaining = riffSize - 4; 111 int hadFmt = 0; 112 int hadData = 0; 113 long dataTell = 0L; 114 while (remaining >= 8) { 115 unsigned char chunk[8]; 116 actual = fread(chunk, sizeof(char), sizeof(chunk), stream); 117 if (actual != sizeof(chunk)) { 118 #ifdef HAVE_STDERR 119 fprintf(stderr, "actual %zu != %zu\n", actual, sizeof(chunk)); 120 #endif 121 goto close; 122 } 123 remaining -= 8; 124 unsigned chunkSize = little4u(&chunk[4]); 125 if (chunkSize > remaining) { 126 #ifdef HAVE_STDERR 127 fprintf(stderr, "chunkSize %u > remaining %zu\n", chunkSize, remaining); 128 #endif 129 goto close; 130 } 131 if (!memcmp(&chunk[0], "fmt ", 4)) { 132 if (hadFmt) { 133 #ifdef HAVE_STDERR 134 fprintf(stderr, "multiple fmt\n"); 135 #endif 136 goto close; 137 } 138 if (chunkSize < 2) { 139 #ifdef HAVE_STDERR 140 fprintf(stderr, "chunkSize %u < 2\n", chunkSize); 141 #endif 142 goto close; 143 } 144 unsigned char fmt[40]; 145 actual = fread(fmt, sizeof(char), 2, stream); 146 if (actual != 2) { 147 #ifdef HAVE_STDERR 148 fprintf(stderr, "actual %zu != 2\n", actual); 149 #endif 150 goto close; 151 } 152 unsigned format = little2u(&fmt[0]); 153 size_t minSize = 0; 154 switch (format) { 155 case WAVE_FORMAT_PCM: 156 case WAVE_FORMAT_IEEE_FLOAT: 157 minSize = 16; 158 break; 159 case WAVE_FORMAT_EXTENSIBLE: 160 minSize = 40; 161 break; 162 default: 163 #ifdef HAVE_STDERR 164 fprintf(stderr, "unsupported format %u\n", format); 165 #endif 166 goto close; 167 } 168 if (chunkSize < minSize) { 169 #ifdef HAVE_STDERR 170 fprintf(stderr, "chunkSize %u < minSize %zu\n", chunkSize, minSize); 171 #endif 172 goto close; 173 } 174 actual = fread(&fmt[2], sizeof(char), minSize - 2, stream); 175 if (actual != minSize - 2) { 176 #ifdef HAVE_STDERR 177 fprintf(stderr, "actual %zu != %zu\n", actual, minSize - 16); 178 #endif 179 goto close; 180 } 181 if (chunkSize > minSize) { 182 fseek(stream, (long) (chunkSize - minSize), SEEK_CUR); 183 } 184 unsigned channels = little2u(&fmt[2]); 185 if ((channels < 1) || (channels > FCC_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 (info->channels > 0 && info->channels <= FCC_8) && 303 ((info->format & SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV) && 304 (sub == SF_FORMAT_PCM_16 || sub == SF_FORMAT_PCM_U8 || sub == SF_FORMAT_FLOAT || 305 sub == SF_FORMAT_PCM_24 || sub == SF_FORMAT_PCM_32) 306 )) { 307 return NULL; 308 } 309 FILE *stream = fopen(path, "w+b"); 310 if (stream == NULL) { 311 #ifdef HAVE_STDERR 312 fprintf(stderr, "fopen %s failed errno %d\n", path, errno); 313 #endif 314 return NULL; 315 } 316 unsigned char wav[58]; 317 memset(wav, 0, sizeof(wav)); 318 memcpy(wav, "RIFF", 4); 319 memcpy(&wav[8], "WAVEfmt ", 8); 320 if (sub == SF_FORMAT_FLOAT) { 321 wav[4] = 50; // riffSize 322 wav[16] = 18; // fmtSize 323 wav[20] = WAVE_FORMAT_IEEE_FLOAT; 324 } else { 325 wav[4] = 36; // riffSize 326 wav[16] = 16; // fmtSize 327 wav[20] = WAVE_FORMAT_PCM; 328 } 329 wav[22] = info->channels; 330 write4u(&wav[24], info->samplerate); 331 unsigned bitsPerSample; 332 switch (sub) { 333 case SF_FORMAT_PCM_16: 334 bitsPerSample = 16; 335 break; 336 case SF_FORMAT_PCM_U8: 337 bitsPerSample = 8; 338 break; 339 case SF_FORMAT_FLOAT: 340 bitsPerSample = 32; 341 break; 342 case SF_FORMAT_PCM_24: 343 bitsPerSample = 24; 344 break; 345 case SF_FORMAT_PCM_32: 346 bitsPerSample = 32; 347 break; 348 default: // not reachable 349 bitsPerSample = 0; 350 break; 351 } 352 unsigned blockAlignment = (bitsPerSample >> 3) * info->channels; 353 unsigned byteRate = info->samplerate * blockAlignment; 354 write4u(&wav[28], byteRate); 355 wav[32] = blockAlignment; 356 wav[34] = bitsPerSample; 357 size_t extra = 0; 358 if (sub == SF_FORMAT_FLOAT) { 359 memcpy(&wav[38], "fact", 4); 360 wav[42] = 4; 361 memcpy(&wav[50], "data", 4); 362 extra = 14; 363 } else 364 memcpy(&wav[36], "data", 4); 365 // dataSize is initially zero 366 (void) fwrite(wav, 44 + extra, 1, stream); 367 SNDFILE *handle = (SNDFILE *) malloc(sizeof(SNDFILE)); 368 handle->mode = SFM_WRITE; 369 handle->temp = NULL; 370 handle->stream = stream; 371 handle->bytesPerFrame = blockAlignment; 372 handle->remaining = 0; 373 handle->info = *info; 374 return handle; 375 } 376 377 SNDFILE *sf_open(const char *path, int mode, SF_INFO *info) 378 { 379 if (path == NULL || info == NULL) { 380 #ifdef HAVE_STDERR 381 fprintf(stderr, "path=%p info=%p\n", path, info); 382 #endif 383 return NULL; 384 } 385 switch (mode) { 386 case SFM_READ: 387 return sf_open_read(path, info); 388 case SFM_WRITE: 389 return sf_open_write(path, info); 390 default: 391 #ifdef HAVE_STDERR 392 fprintf(stderr, "mode=%d\n", mode); 393 #endif 394 return NULL; 395 } 396 } 397 398 void sf_close(SNDFILE *handle) 399 { 400 if (handle == NULL) 401 return; 402 free(handle->temp); 403 if (handle->mode == SFM_WRITE) { 404 (void) fflush(handle->stream); 405 rewind(handle->stream); 406 unsigned char wav[58]; 407 size_t extra = (handle->info.format & SF_FORMAT_SUBMASK) == SF_FORMAT_FLOAT ? 14 : 0; 408 (void) fread(wav, 44 + extra, 1, handle->stream); 409 unsigned dataSize = handle->remaining * handle->bytesPerFrame; 410 write4u(&wav[4], dataSize + 36 + extra); // riffSize 411 write4u(&wav[40 + extra], dataSize); // dataSize 412 rewind(handle->stream); 413 (void) fwrite(wav, 44 + extra, 1, handle->stream); 414 } 415 (void) fclose(handle->stream); 416 free(handle); 417 } 418 419 sf_count_t sf_readf_short(SNDFILE *handle, short *ptr, sf_count_t desiredFrames) 420 { 421 if (handle == NULL || handle->mode != SFM_READ || ptr == NULL || !handle->remaining || 422 desiredFrames <= 0) { 423 return 0; 424 } 425 if (handle->remaining < (size_t) desiredFrames) { 426 desiredFrames = handle->remaining; 427 } 428 // does not check for numeric overflow 429 size_t desiredBytes = desiredFrames * handle->bytesPerFrame; 430 size_t actualBytes; 431 void *temp = NULL; 432 unsigned format = handle->info.format & SF_FORMAT_SUBMASK; 433 if (format == SF_FORMAT_PCM_32 || format == SF_FORMAT_FLOAT || format == SF_FORMAT_PCM_24) { 434 temp = malloc(desiredBytes); 435 actualBytes = fread(temp, sizeof(char), desiredBytes, handle->stream); 436 } else { 437 actualBytes = fread(ptr, sizeof(char), desiredBytes, handle->stream); 438 } 439 size_t actualFrames = actualBytes / handle->bytesPerFrame; 440 handle->remaining -= actualFrames; 441 switch (format) { 442 case SF_FORMAT_PCM_U8: 443 memcpy_to_i16_from_u8(ptr, (unsigned char *) ptr, actualFrames * handle->info.channels); 444 break; 445 case SF_FORMAT_PCM_16: 446 if (!isLittleEndian()) 447 my_swab(ptr, actualFrames * handle->info.channels); 448 break; 449 case SF_FORMAT_PCM_32: 450 memcpy_to_i16_from_i32(ptr, (const int *) temp, actualFrames * handle->info.channels); 451 free(temp); 452 break; 453 case SF_FORMAT_FLOAT: 454 memcpy_to_i16_from_float(ptr, (const float *) temp, actualFrames * handle->info.channels); 455 free(temp); 456 break; 457 case SF_FORMAT_PCM_24: 458 memcpy_to_i16_from_p24(ptr, (const uint8_t *) temp, actualFrames * handle->info.channels); 459 free(temp); 460 break; 461 default: 462 memset(ptr, 0, actualFrames * handle->info.channels * sizeof(short)); 463 break; 464 } 465 return actualFrames; 466 } 467 468 sf_count_t sf_readf_float(SNDFILE *handle, float *ptr, sf_count_t desiredFrames) 469 { 470 if (handle == NULL || handle->mode != SFM_READ || ptr == NULL || !handle->remaining || 471 desiredFrames <= 0) { 472 return 0; 473 } 474 if (handle->remaining < (size_t) desiredFrames) { 475 desiredFrames = handle->remaining; 476 } 477 // does not check for numeric overflow 478 size_t desiredBytes = desiredFrames * handle->bytesPerFrame; 479 size_t actualBytes; 480 void *temp = NULL; 481 unsigned format = handle->info.format & SF_FORMAT_SUBMASK; 482 if (format == SF_FORMAT_PCM_16 || format == SF_FORMAT_PCM_U8 || format == SF_FORMAT_PCM_24) { 483 temp = malloc(desiredBytes); 484 actualBytes = fread(temp, sizeof(char), desiredBytes, handle->stream); 485 } else { 486 actualBytes = fread(ptr, sizeof(char), desiredBytes, handle->stream); 487 } 488 size_t actualFrames = actualBytes / handle->bytesPerFrame; 489 handle->remaining -= actualFrames; 490 switch (format) { 491 case SF_FORMAT_PCM_U8: 492 #if 0 493 // TODO - implement 494 memcpy_to_float_from_u8(ptr, (const unsigned char *) temp, 495 actualFrames * handle->info.channels); 496 #endif 497 free(temp); 498 break; 499 case SF_FORMAT_PCM_16: 500 memcpy_to_float_from_i16(ptr, (const short *) temp, actualFrames * handle->info.channels); 501 free(temp); 502 break; 503 case SF_FORMAT_PCM_32: 504 memcpy_to_float_from_i32(ptr, (const int *) ptr, actualFrames * handle->info.channels); 505 break; 506 case SF_FORMAT_FLOAT: 507 break; 508 case SF_FORMAT_PCM_24: 509 memcpy_to_float_from_p24(ptr, (const uint8_t *) temp, actualFrames * handle->info.channels); 510 free(temp); 511 break; 512 default: 513 memset(ptr, 0, actualFrames * handle->info.channels * sizeof(float)); 514 break; 515 } 516 return actualFrames; 517 } 518 519 sf_count_t sf_readf_int(SNDFILE *handle, int *ptr, sf_count_t desiredFrames) 520 { 521 if (handle == NULL || handle->mode != SFM_READ || ptr == NULL || !handle->remaining || 522 desiredFrames <= 0) { 523 return 0; 524 } 525 if (handle->remaining < (size_t) desiredFrames) { 526 desiredFrames = handle->remaining; 527 } 528 // does not check for numeric overflow 529 size_t desiredBytes = desiredFrames * handle->bytesPerFrame; 530 void *temp = NULL; 531 unsigned format = handle->info.format & SF_FORMAT_SUBMASK; 532 size_t actualBytes; 533 if (format == SF_FORMAT_PCM_16 || format == SF_FORMAT_PCM_U8 || format == SF_FORMAT_PCM_24) { 534 temp = malloc(desiredBytes); 535 actualBytes = fread(temp, sizeof(char), desiredBytes, handle->stream); 536 } else { 537 actualBytes = fread(ptr, sizeof(char), desiredBytes, handle->stream); 538 } 539 size_t actualFrames = actualBytes / handle->bytesPerFrame; 540 handle->remaining -= actualFrames; 541 switch (format) { 542 case SF_FORMAT_PCM_U8: 543 #if 0 544 // TODO - implement 545 memcpy_to_i32_from_u8(ptr, (const unsigned char *) temp, 546 actualFrames * handle->info.channels); 547 #endif 548 free(temp); 549 break; 550 case SF_FORMAT_PCM_16: 551 memcpy_to_i32_from_i16(ptr, (const short *) temp, actualFrames * handle->info.channels); 552 free(temp); 553 break; 554 case SF_FORMAT_PCM_32: 555 break; 556 case SF_FORMAT_FLOAT: 557 memcpy_to_i32_from_float(ptr, (const float *) ptr, actualFrames * handle->info.channels); 558 break; 559 case SF_FORMAT_PCM_24: 560 memcpy_to_i32_from_p24(ptr, (const uint8_t *) temp, actualFrames * handle->info.channels); 561 free(temp); 562 break; 563 default: 564 memset(ptr, 0, actualFrames * handle->info.channels * sizeof(int)); 565 break; 566 } 567 return actualFrames; 568 } 569 570 sf_count_t sf_writef_short(SNDFILE *handle, const short *ptr, sf_count_t desiredFrames) 571 { 572 if (handle == NULL || handle->mode != SFM_WRITE || ptr == NULL || desiredFrames <= 0) 573 return 0; 574 size_t desiredBytes = desiredFrames * handle->bytesPerFrame; 575 size_t actualBytes = 0; 576 switch (handle->info.format & SF_FORMAT_SUBMASK) { 577 case SF_FORMAT_PCM_U8: 578 handle->temp = realloc(handle->temp, desiredBytes); 579 memcpy_to_u8_from_i16(handle->temp, ptr, desiredBytes); 580 actualBytes = fwrite(handle->temp, sizeof(char), desiredBytes, handle->stream); 581 break; 582 case SF_FORMAT_PCM_16: 583 // does not check for numeric overflow 584 if (isLittleEndian()) { 585 actualBytes = fwrite(ptr, sizeof(char), desiredBytes, handle->stream); 586 } else { 587 handle->temp = realloc(handle->temp, desiredBytes); 588 memcpy(handle->temp, ptr, desiredBytes); 589 my_swab((short *) handle->temp, desiredFrames * handle->info.channels); 590 actualBytes = fwrite(handle->temp, sizeof(char), desiredBytes, handle->stream); 591 } 592 break; 593 case SF_FORMAT_FLOAT: 594 handle->temp = realloc(handle->temp, desiredBytes); 595 memcpy_to_float_from_i16((float *) handle->temp, ptr, 596 desiredFrames * handle->info.channels); 597 actualBytes = fwrite(handle->temp, sizeof(char), desiredBytes, handle->stream); 598 break; 599 default: 600 break; 601 } 602 size_t actualFrames = actualBytes / handle->bytesPerFrame; 603 handle->remaining += actualFrames; 604 return actualFrames; 605 } 606 607 sf_count_t sf_writef_float(SNDFILE *handle, const float *ptr, sf_count_t desiredFrames) 608 { 609 if (handle == NULL || handle->mode != SFM_WRITE || ptr == NULL || desiredFrames <= 0) 610 return 0; 611 size_t desiredBytes = desiredFrames * handle->bytesPerFrame; 612 size_t actualBytes = 0; 613 switch (handle->info.format & SF_FORMAT_SUBMASK) { 614 case SF_FORMAT_FLOAT: 615 actualBytes = fwrite(ptr, sizeof(char), desiredBytes, handle->stream); 616 break; 617 case SF_FORMAT_PCM_16: 618 handle->temp = realloc(handle->temp, desiredBytes); 619 memcpy_to_i16_from_float((short *) handle->temp, ptr, 620 desiredFrames * handle->info.channels); 621 actualBytes = fwrite(handle->temp, sizeof(char), desiredBytes, handle->stream); 622 break; 623 case SF_FORMAT_PCM_U8: // transcoding from float to byte not yet implemented 624 default: 625 break; 626 } 627 size_t actualFrames = actualBytes / handle->bytesPerFrame; 628 handle->remaining += actualFrames; 629 return actualFrames; 630 } 631 632 sf_count_t sf_writef_int(SNDFILE *handle, const int *ptr, sf_count_t desiredFrames) 633 { 634 if (handle == NULL || handle->mode != SFM_WRITE || ptr == NULL || desiredFrames <= 0) 635 return 0; 636 size_t desiredBytes = desiredFrames * handle->bytesPerFrame; 637 size_t actualBytes = 0; 638 switch (handle->info.format & SF_FORMAT_SUBMASK) { 639 case SF_FORMAT_PCM_32: 640 actualBytes = fwrite(ptr, sizeof(char), desiredBytes, handle->stream); 641 break; 642 default: // transcoding from other formats not yet implemented 643 break; 644 } 645 size_t actualFrames = actualBytes / handle->bytesPerFrame; 646 handle->remaining += actualFrames; 647 return actualFrames; 648 } 649