blob: f028685a754ede9f19d4f1d403ffaaf5cca5fb32 [file] [log] [blame]
/*
* 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_ {
FILE *stream;
size_t bytesPerFrame;
size_t remaining;
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;
}
}
SNDFILE *sf_open(const char *path, int mode, SF_INFO *info)
{
if (path == NULL || mode != SFM_READ || info == NULL)
return NULL;
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 < 44)
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->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;
}
void sf_close(SNDFILE *handle)
{
if (handle == NULL)
return;
(void) fclose(handle->stream);
handle->stream = NULL;
handle->remaining = 0;
}
sf_count_t sf_readf_short(SNDFILE *handle, short *ptr, sf_count_t desiredFrames)
{
if (handle == NULL || 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;
}