| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <audio_utils/sndfile.h> |
| #include <audio_utils/primitives.h> |
| #include <stdio.h> |
| #include <string.h> |
| |
| struct SNDFILE_ { |
| int mode; |
| uint8_t *temp; // realloc buffer used for shrinking 16 bits to 8 bits and byte-swapping |
| FILE *stream; |
| size_t bytesPerFrame; |
| size_t remaining; // frames unread for SFM_READ, frames written for SFM_WRITE |
| SF_INFO info; |
| }; |
| |
| static unsigned little2u(unsigned char *ptr) |
| { |
| return (ptr[1] << 8) + ptr[0]; |
| } |
| |
| static unsigned little4u(unsigned char *ptr) |
| { |
| return (ptr[3] << 24) + (ptr[2] << 16) + (ptr[1] << 8) + ptr[0]; |
| } |
| |
| static int isLittleEndian(void) |
| { |
| static const short one = 1; |
| return *((const char *) &one) == 1; |
| } |
| |
| static void swab(short *ptr, size_t numToSwap) |
| { |
| while (numToSwap > 0) { |
| *ptr = little2u((unsigned char *) ptr); |
| --numToSwap; |
| ++ptr; |
| } |
| } |
| |
| static SNDFILE *sf_open_read(const char *path, SF_INFO *info) |
| { |
| FILE *stream = fopen(path, "rb"); |
| if (stream == NULL) |
| return NULL; |
| // don't attempt to parse all valid forms, just the most common one |
| unsigned char wav[44]; |
| size_t actual; |
| actual = fread(wav, sizeof(char), sizeof(wav), stream); |
| if (actual != sizeof(wav)) |
| return NULL; |
| for (;;) { |
| if (memcmp(wav, "RIFF", 4)) |
| break; |
| unsigned riffSize = little4u(&wav[4]); |
| if (riffSize < 36) |
| break; |
| if (memcmp(&wav[8], "WAVEfmt ", 8)) |
| break; |
| unsigned fmtsize = little4u(&wav[16]); |
| if (fmtsize != 16) |
| break; |
| unsigned format = little2u(&wav[20]); |
| if (format != 1) // PCM |
| break; |
| unsigned channels = little2u(&wav[22]); |
| if (channels != 1 && channels != 2) |
| break; |
| unsigned samplerate = little4u(&wav[24]); |
| if (samplerate == 0) |
| break; |
| // ignore byte rate |
| // ignore block alignment |
| unsigned bitsPerSample = little2u(&wav[34]); |
| if (bitsPerSample != 8 && bitsPerSample != 16) |
| break; |
| unsigned bytesPerFrame = (bitsPerSample >> 3) * channels; |
| if (memcmp(&wav[36], "data", 4)) |
| break; |
| unsigned dataSize = little4u(&wav[40]); |
| SNDFILE *handle = (SNDFILE *) malloc(sizeof(SNDFILE)); |
| handle->mode = SFM_READ; |
| handle->temp = NULL; |
| handle->stream = stream; |
| handle->bytesPerFrame = bytesPerFrame; |
| handle->remaining = dataSize / bytesPerFrame; |
| handle->info.frames = handle->remaining; |
| handle->info.samplerate = samplerate; |
| handle->info.channels = channels; |
| handle->info.format = SF_FORMAT_WAV; |
| if (bitsPerSample == 8) |
| handle->info.format |= SF_FORMAT_PCM_U8; |
| else |
| handle->info.format |= SF_FORMAT_PCM_16; |
| *info = handle->info; |
| return handle; |
| } |
| return NULL; |
| } |
| |
| static void write4u(unsigned char *ptr, unsigned u) |
| { |
| ptr[0] = u; |
| ptr[1] = u >> 8; |
| ptr[2] = u >> 16; |
| ptr[3] = u >> 24; |
| } |
| |
| static SNDFILE *sf_open_write(const char *path, SF_INFO *info) |
| { |
| if (!( |
| (info->samplerate > 0) && |
| (info->channels == 1 || info->channels == 2) && |
| ((info->format & SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV) && |
| ((info->format & SF_FORMAT_SUBMASK) == SF_FORMAT_PCM_16 || |
| (info->format & SF_FORMAT_SUBMASK) == SF_FORMAT_PCM_U8) |
| )) { |
| return NULL; |
| } |
| FILE *stream = fopen(path, "w+b"); |
| unsigned char wav[44]; |
| memset(wav, 0, sizeof(wav)); |
| memcpy(wav, "RIFF", 4); |
| wav[4] = 36; // riffSize |
| memcpy(&wav[8], "WAVEfmt ", 8); |
| wav[16] = 16; // fmtsize |
| wav[20] = 1; // format = PCM |
| wav[22] = info->channels; |
| write4u(&wav[24], info->samplerate); |
| unsigned bitsPerSample = (info->format & SF_FORMAT_SUBMASK) == SF_FORMAT_PCM_16 ? 16 : 8; |
| unsigned blockAlignment = (bitsPerSample >> 3) * info->channels; |
| unsigned byteRate = info->samplerate * blockAlignment; |
| write4u(&wav[28], byteRate); |
| wav[32] = blockAlignment; |
| wav[34] = bitsPerSample; |
| memcpy(&wav[36], "data", 4); |
| // dataSize is initially zero |
| (void) fwrite(wav, sizeof(wav), 1, stream); |
| SNDFILE *handle = (SNDFILE *) malloc(sizeof(SNDFILE)); |
| handle->mode = SFM_WRITE; |
| handle->temp = NULL; |
| handle->stream = stream; |
| handle->bytesPerFrame = blockAlignment; |
| handle->remaining = 0; |
| handle->info = *info; |
| return handle; |
| } |
| |
| SNDFILE *sf_open(const char *path, int mode, SF_INFO *info) |
| { |
| if (path == NULL || info == NULL) |
| return NULL; |
| switch (mode) { |
| case SFM_READ: |
| return sf_open_read(path, info); |
| case SFM_WRITE: |
| return sf_open_write(path, info); |
| default: |
| return NULL; |
| } |
| } |
| |
| void sf_close(SNDFILE *handle) |
| { |
| if (handle == NULL) |
| return; |
| free(handle->temp); |
| if (handle->mode == SFM_WRITE) { |
| (void) fflush(handle->stream); |
| rewind(handle->stream); |
| unsigned char wav[44]; |
| (void) fread(wav, sizeof(wav), 1, handle->stream); |
| unsigned dataSize = handle->remaining * handle->bytesPerFrame; |
| write4u(&wav[4], dataSize + 36); // riffSize |
| write4u(&wav[40], dataSize); // dataSize |
| rewind(handle->stream); |
| (void) fwrite(wav, sizeof(wav), 1, handle->stream); |
| } |
| (void) fclose(handle->stream); |
| free(handle); |
| } |
| |
| sf_count_t sf_readf_short(SNDFILE *handle, short *ptr, sf_count_t desiredFrames) |
| { |
| if (handle == NULL || handle->mode != SFM_READ || ptr == NULL || !handle->remaining || |
| desiredFrames <= 0) { |
| return 0; |
| } |
| if (handle->remaining < (size_t) desiredFrames) |
| desiredFrames = handle->remaining; |
| size_t desiredBytes = desiredFrames * handle->bytesPerFrame; |
| // does not check for numeric overflow |
| size_t actualBytes = fread(ptr, sizeof(char), desiredBytes, handle->stream); |
| size_t actualFrames = actualBytes / handle->bytesPerFrame; |
| handle->remaining -= actualFrames; |
| switch (handle->info.format & SF_FORMAT_SUBMASK) { |
| case SF_FORMAT_PCM_U8: |
| memcpy_to_i16_from_u8(ptr, (unsigned char *) ptr, actualFrames * handle->info.channels); |
| break; |
| case SF_FORMAT_PCM_16: |
| if (!isLittleEndian()) |
| swab(ptr, actualFrames * handle->info.channels); |
| break; |
| } |
| return actualFrames; |
| } |
| |
| sf_count_t sf_writef_short(SNDFILE *handle, const short *ptr, sf_count_t desiredFrames) |
| { |
| if (handle == NULL || handle->mode != SFM_WRITE || ptr == NULL || desiredFrames <= 0) |
| return 0; |
| size_t desiredBytes = desiredFrames * handle->bytesPerFrame; |
| size_t actualBytes = 0; |
| switch (handle->info.format & SF_FORMAT_SUBMASK) { |
| case SF_FORMAT_PCM_U8: |
| handle->temp = realloc(handle->temp, desiredBytes); |
| memcpy_to_u8_from_i16(handle->temp, ptr, desiredBytes); |
| actualBytes = fwrite(handle->temp, sizeof(char), desiredBytes, handle->stream); |
| break; |
| case SF_FORMAT_PCM_16: |
| // does not check for numeric overflow |
| if (isLittleEndian()) { |
| actualBytes = fwrite(ptr, sizeof(char), desiredBytes, handle->stream); |
| } else { |
| handle->temp = realloc(handle->temp, desiredBytes); |
| memcpy(handle->temp, ptr, desiredBytes); |
| swab((short *) handle->temp, desiredFrames * handle->info.channels); |
| actualBytes = fwrite(handle->temp, sizeof(char), desiredBytes, handle->stream); |
| } |
| break; |
| } |
| size_t actualFrames = actualBytes / handle->bytesPerFrame; |
| handle->remaining += actualFrames; |
| return actualFrames; |
| } |