tinysndfile.c revision eae13ddb9a4fdedf3895a10cdf5c0a4aefe0ff60
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#include <stdio.h>
20#include <string.h>
21
22struct SNDFILE_ {
23    int mode;
24    uint8_t *temp;  // realloc buffer used for shrinking 16 bits to 8 bits and byte-swapping
25    FILE *stream;
26    size_t bytesPerFrame;
27    size_t remaining;   // frames unread for SFM_READ, frames written for SFM_WRITE
28    SF_INFO info;
29};
30
31static unsigned little2u(unsigned char *ptr)
32{
33    return (ptr[1] << 8) + ptr[0];
34}
35
36static unsigned little4u(unsigned char *ptr)
37{
38    return (ptr[3] << 24) + (ptr[2] << 16) + (ptr[1] << 8) + ptr[0];
39}
40
41static int isLittleEndian(void)
42{
43    static const short one = 1;
44    return *((const char *) &one) == 1;
45}
46
47static void swab(short *ptr, size_t numToSwap)
48{
49    while (numToSwap > 0) {
50        *ptr = little2u((unsigned char *) ptr);
51        --numToSwap;
52        ++ptr;
53    }
54}
55
56static SNDFILE *sf_open_read(const char *path, SF_INFO *info)
57{
58    FILE *stream = fopen(path, "rb");
59    if (stream == NULL)
60        return NULL;
61    // don't attempt to parse all valid forms, just the most common one
62    unsigned char wav[44];
63    size_t actual;
64    actual = fread(wav, sizeof(char), sizeof(wav), stream);
65    if (actual != sizeof(wav))
66        return NULL;
67    for (;;) {
68        if (memcmp(wav, "RIFF", 4))
69            break;
70        unsigned riffSize = little4u(&wav[4]);
71        if (riffSize < 36)
72            break;
73        if (memcmp(&wav[8], "WAVEfmt ", 8))
74            break;
75        unsigned fmtsize = little4u(&wav[16]);
76        if (fmtsize != 16)
77            break;
78        unsigned format = little2u(&wav[20]);
79        if (format != 1)    // PCM
80            break;
81        unsigned channels = little2u(&wav[22]);
82        if (channels != 1 && channels != 2)
83            break;
84        unsigned samplerate = little4u(&wav[24]);
85        if (samplerate == 0)
86            break;
87        // ignore byte rate
88        // ignore block alignment
89        unsigned bitsPerSample = little2u(&wav[34]);
90        if (bitsPerSample != 8 && bitsPerSample != 16)
91            break;
92        unsigned bytesPerFrame = (bitsPerSample >> 3) * channels;
93        if (memcmp(&wav[36], "data", 4))
94            break;
95        unsigned dataSize = little4u(&wav[40]);
96        SNDFILE *handle = (SNDFILE *) malloc(sizeof(SNDFILE));
97        handle->mode = SFM_READ;
98        handle->temp = NULL;
99        handle->stream = stream;
100        handle->bytesPerFrame = bytesPerFrame;
101        handle->remaining = dataSize / bytesPerFrame;
102        handle->info.frames = handle->remaining;
103        handle->info.samplerate = samplerate;
104        handle->info.channels = channels;
105        handle->info.format = SF_FORMAT_WAV;
106        if (bitsPerSample == 8)
107            handle->info.format |= SF_FORMAT_PCM_U8;
108        else
109            handle->info.format |= SF_FORMAT_PCM_16;
110        *info = handle->info;
111        return handle;
112    }
113    return NULL;
114}
115
116static void write4u(unsigned char *ptr, unsigned u)
117{
118    ptr[0] = u;
119    ptr[1] = u >> 8;
120    ptr[2] = u >> 16;
121    ptr[3] = u >> 24;
122}
123
124static SNDFILE *sf_open_write(const char *path, SF_INFO *info)
125{
126    if (!(
127            (info->samplerate > 0) &&
128            (info->channels == 1 || info->channels == 2) &&
129            ((info->format & SF_FORMAT_TYPEMASK) == SF_FORMAT_WAV) &&
130            ((info->format & SF_FORMAT_SUBMASK) == SF_FORMAT_PCM_16 ||
131             (info->format & SF_FORMAT_SUBMASK) == SF_FORMAT_PCM_U8)
132          )) {
133        return NULL;
134    }
135    FILE *stream = fopen(path, "w+b");
136    unsigned char wav[44];
137    memset(wav, 0, sizeof(wav));
138    memcpy(wav, "RIFF", 4);
139    wav[4] = 36;    // riffSize
140    memcpy(&wav[8], "WAVEfmt ", 8);
141    wav[16] = 16;   // fmtsize
142    wav[20] = 1;    // format = PCM
143    wav[22] = info->channels;
144    write4u(&wav[24], info->samplerate);
145    unsigned bitsPerSample = (info->format & SF_FORMAT_SUBMASK) == SF_FORMAT_PCM_16 ? 16 : 8;
146    unsigned blockAlignment = (bitsPerSample >> 3) * info->channels;
147    unsigned byteRate = info->samplerate * blockAlignment;
148    write4u(&wav[28], byteRate);
149    wav[32] = blockAlignment;
150    wav[34] = bitsPerSample;
151    memcpy(&wav[36], "data", 4);
152    // dataSize is initially zero
153    (void) fwrite(wav, sizeof(wav), 1, stream);
154    SNDFILE *handle = (SNDFILE *) malloc(sizeof(SNDFILE));
155    handle->mode = SFM_WRITE;
156    handle->temp = NULL;
157    handle->stream = stream;
158    handle->bytesPerFrame = blockAlignment;
159    handle->remaining = 0;
160    handle->info = *info;
161    return handle;
162}
163
164SNDFILE *sf_open(const char *path, int mode, SF_INFO *info)
165{
166    if (path == NULL || info == NULL)
167        return NULL;
168    switch (mode) {
169    case SFM_READ:
170        return sf_open_read(path, info);
171    case SFM_WRITE:
172        return sf_open_write(path, info);
173    default:
174        return NULL;
175    }
176}
177
178void sf_close(SNDFILE *handle)
179{
180    if (handle == NULL)
181        return;
182    free(handle->temp);
183    if (handle->mode == SFM_WRITE) {
184        (void) fflush(handle->stream);
185        rewind(handle->stream);
186        unsigned char wav[44];
187        (void) fread(wav, sizeof(wav), 1, handle->stream);
188        unsigned dataSize = handle->remaining * handle->bytesPerFrame;
189        write4u(&wav[4], dataSize + 36);    // riffSize
190        write4u(&wav[40], dataSize);        // dataSize
191        rewind(handle->stream);
192        (void) fwrite(wav, sizeof(wav), 1, handle->stream);
193    }
194    (void) fclose(handle->stream);
195    free(handle);
196}
197
198sf_count_t sf_readf_short(SNDFILE *handle, short *ptr, sf_count_t desiredFrames)
199{
200    if (handle == NULL || handle->mode != SFM_READ || ptr == NULL || !handle->remaining ||
201            desiredFrames <= 0) {
202        return 0;
203    }
204    if (handle->remaining < (size_t) desiredFrames)
205        desiredFrames = handle->remaining;
206    size_t desiredBytes = desiredFrames * handle->bytesPerFrame;
207    // does not check for numeric overflow
208    size_t actualBytes = fread(ptr, sizeof(char), desiredBytes, handle->stream);
209    size_t actualFrames = actualBytes / handle->bytesPerFrame;
210    handle->remaining -= actualFrames;
211    switch (handle->info.format & SF_FORMAT_SUBMASK) {
212    case SF_FORMAT_PCM_U8:
213        memcpy_to_i16_from_u8(ptr, (unsigned char *) ptr, actualFrames * handle->info.channels);
214        break;
215    case SF_FORMAT_PCM_16:
216        if (!isLittleEndian())
217            swab(ptr, actualFrames * handle->info.channels);
218        break;
219    }
220    return actualFrames;
221}
222
223sf_count_t sf_writef_short(SNDFILE *handle, const short *ptr, sf_count_t desiredFrames)
224{
225    if (handle == NULL || handle->mode != SFM_WRITE || ptr == NULL || desiredFrames <= 0)
226        return 0;
227    size_t desiredBytes = desiredFrames * handle->bytesPerFrame;
228    size_t actualBytes = 0;
229    switch (handle->info.format & SF_FORMAT_SUBMASK) {
230    case SF_FORMAT_PCM_U8:
231        handle->temp = realloc(handle->temp, desiredBytes);
232        memcpy_to_u8_from_i16(handle->temp, ptr, desiredBytes);
233        actualBytes = fwrite(handle->temp, sizeof(char), desiredBytes, handle->stream);
234        break;
235    case SF_FORMAT_PCM_16:
236        // does not check for numeric overflow
237        if (isLittleEndian()) {
238            actualBytes = fwrite(ptr, sizeof(char), desiredBytes, handle->stream);
239        } else {
240            handle->temp = realloc(handle->temp, desiredBytes);
241            memcpy(handle->temp, ptr, desiredBytes);
242            swab((short *) handle->temp, desiredFrames * handle->info.channels);
243            actualBytes = fwrite(handle->temp, sizeof(char), desiredBytes, handle->stream);
244        }
245        break;
246    }
247    size_t actualFrames = actualBytes / handle->bytesPerFrame;
248    handle->remaining += actualFrames;
249    return actualFrames;
250}
251