You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

578 lines
14 KiB

/*
* wavlib.c - simple WAV I/O library interface
* Copyright (C) 2006-2010 Francesco Romani <fromani at gmail dot com>
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
#include "wavlib.h"
#include "platform.h"
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
/*************************************************************************
* utilties *
*************************************************************************/
#define WAV_BUF_SIZE (1024)
#if (!defined HAVE_BYTESWAP && defined WAV_BIG_ENDIAN)
static uint16_t bswap_16(uint16_t x)
{
return (((x & 0xff00) >> 8) | ((x & 0x00ff) << 8));
}
static uint32_t bswap_32(uint32_t x)
{
return (((x & 0xff000000UL) >> 24) |
((x & 0x00ff0000UL) >> 8) |
((x & 0x0000ff00UL) << 8) |
((x & 0x000000ffUL) << 24));
}
static uint64_t bswap_64(uint64_t x)
{
return (((x & 0xff00000000000000ULL) >> 56) |
((x & 0x00ff000000000000ULL) >> 40) |
((x & 0x0000ff0000000000ULL) >> 24) |
((x & 0x000000ff00000000ULL) >> 8) |
((x & 0x00000000ff000000ULL) << 8) |
((x & 0x0000000000ff0000ULL) << 24) |
((x & 0x000000000000ff00ULL) << 40) |
((x & 0x00000000000000ffULL) << 56));
}
#endif
#if (!defined WAV_BIG_ENDIAN && !defined WAV_LITTLE_ENDIAN)
#error "you must define either LITTLE_ENDIAN or BIG_ENDIAN"
#endif
#if (defined WAV_BIG_ENDIAN && defined WAV_LITTLE_ENDIAN)
#error "you CAN'T define BOTH LITTLE_ENDIAN and BIG_ENDIAN"
#endif
#if defined WAV_BIG_ENDIAN
#define htol_16(x) bswap_16(x)
#define htol_32(x) bswap_32(x)
#define htol_64(x) bswap_64(x)
#elif defined WAV_LITTLE_ENDIAN
#define htol_16(x) (x)
#define htol_32(x) (x)
#define htol_64(x) (x)
#endif
/* often used out-of-order */
#define make_wav_get_bits(s) \
static inline uint##s##_t wav_get_bits##s(uint8_t *d) \
{ \
return htol_##s(*((uint##s##_t*)d)); \
}
/* often used sequentially */
#define make_wav_put_bits(s) \
static inline uint8_t *wav_put_bits##s(uint8_t *d, uint##s##_t u) \
{ \
*((uint##s##_t*)d) = htol_##s(u); \
return (d + (s / 8)); \
}
make_wav_get_bits(16)
make_wav_get_bits(32)
make_wav_get_bits(64)
make_wav_put_bits(16)
make_wav_put_bits(32)
make_wav_put_bits(64)
static inline uint32_t make_tag(uint8_t a, uint8_t b, uint8_t c, uint8_t d)
{
return (a | (b << 8) | (c << 16) | (d << 24));
}
/*************************************************************************
* header data *
*************************************************************************/
/*
* WAVE header:
*
* TAG: 'RIFF' 4 bytes
* LENGTH: 4 bytes
* TAG: 'WAVE' 4 bytes
*
* TAG: 'fmt ' 4 bytes
* LENGTH: 4 bytes
*
* +
* FORMAT: 2 bytes |
* CHANNELS: 2 bytes |
* SAMPLES: 4 bytes | simple WAV format:
* AVGBYTES: 4 bytes | 16 byte
* BLKALIGN: 2 bytes |
* BITS: 2 bytes |
* +
*
* TAG: 'data' 4 bytes
* LENGTH: 4 bytes
*
* ----------------------------
* TOTAL wav header: 44 bytes
*/
#define WAV_HEADER_LEN (44)
#define WAV_FORMAT_LEN (16)
#define PCM_ID (0x1)
/*************************************************************************
* core data/routines *
*************************************************************************/
#define WAV_SET_ERROR(errp, code) \
if (errp != NULL) { \
*errp = code; \
}
struct wav_ {
int fd;
int header_done;
int close_fd;
int has_pipe;
WAVMode mode;
WAVError error;
uint32_t len;
uint32_t bitrate;
uint16_t bits;
uint16_t channels;
uint32_t rate;
uint16_t block_align;
};
const char *wav_strerror(WAVError err)
{
const char *s = NULL;
switch (err) {
case WAV_SUCCESS:
s = "no error";
break;
case WAV_NO_MEM:
s = "can't acquire the needed amount of memory";
break;
case WAV_IO_ERROR:
s = "error while performing I/O operation";
break;
case WAV_BAD_FORMAT:
s = "incorrect/unrecognized WAV data";
break;
case WAV_BAD_PARAM:
s = "bad/unknown parameter for this operation";
break;
case WAV_UNSUPPORTED:
s = "not yet supported by wavlib";
break;
default:
s = NULL;
break;
}
return s;
}
static int wav_parse_header(WAV handle, WAVError *err)
{
uint8_t hdr[WAV_HEADER_LEN];
ssize_t r = 0;
uint16_t wav_fmt = 0;
uint32_t fmt_len = 0;
if (!handle || handle->fd == -1 || !(handle->mode & WAV_READ)) {
return -1;
}
r = plat_read(handle->fd, hdr, WAV_HEADER_LEN);
if (r != WAV_HEADER_LEN) {
WAV_SET_ERROR(err, WAV_BAD_FORMAT);
goto bad_wav;
}
if ((wav_get_bits32(hdr) != make_tag('R', 'I', 'F', 'F'))
|| (wav_get_bits32(hdr + 8) != make_tag('W', 'A', 'V', 'E'))
|| (wav_get_bits32(hdr + 12) != make_tag('f', 'm', 't', ' '))) {
WAV_SET_ERROR(err, WAV_BAD_FORMAT);
goto bad_wav;
}
fmt_len = wav_get_bits32(hdr + 16);
wav_fmt = wav_get_bits16(hdr + 20);
if (fmt_len != WAV_FORMAT_LEN || wav_fmt != PCM_ID) {
WAV_SET_ERROR(err, WAV_UNSUPPORTED);
goto bad_wav;
}
handle->len = wav_get_bits32(hdr + 4);
handle->channels = wav_get_bits16(hdr + 22);
handle->rate = wav_get_bits32(hdr + 24);
handle->bitrate = (wav_get_bits32(hdr + 28) * 8) / 1000;
handle->block_align = wav_get_bits16(hdr + 32);
handle->bits = wav_get_bits16(hdr + 34);
/* skip 'data' tag (4 bytes) */
handle->len = wav_get_bits32(hdr + 40);
return 0;
bad_wav:
lseek(handle->fd, 0, SEEK_SET);
return 1;
}
int wav_write_header(WAV handle, int force)
{
uint8_t hdr[WAV_HEADER_LEN];
uint8_t *ph = hdr;
off_t pos = 0, ret = 0;
ssize_t w = 0;
if (!handle) {
return -1;
}
if (!force && handle->header_done) {
return 0;
}
if (handle->bits != 0
&& (handle->bits != 8 && handle->bits != 16)) {
/* bits == 0 -> not specified (so it's good) */
WAV_SET_ERROR(&(handle->error), WAV_UNSUPPORTED);
return -1;
}
if (!handle->has_pipe) {
pos = lseek(handle->fd, 0, SEEK_CUR);
ret = lseek(handle->fd, 0, SEEK_SET);
if (ret == (off_t)-1) {
return 1;
}
}
ph = wav_put_bits32(ph, make_tag('R', 'I', 'F', 'F'));
ph = wav_put_bits32(ph, handle->len + WAV_HEADER_LEN - 8);
ph = wav_put_bits32(ph, make_tag('W', 'A', 'V', 'E'));
ph = wav_put_bits32(ph, make_tag('f', 'm', 't', ' '));
ph = wav_put_bits32(ph, WAV_FORMAT_LEN);
/* format */
ph = wav_put_bits16(ph, PCM_ID);
/* wave format, only plain PCM supported, yet */
ph = wav_put_bits16(ph, handle->channels);
/* number of channels */
ph = wav_put_bits32(ph, handle->rate);
/* sample rate */
ph = wav_put_bits32(ph, (handle->bitrate * 1000)/8);
/* average bytes per second (aka bitrate) */
ph = wav_put_bits16(ph, ((handle->channels * handle->bits) / 8));
/* block alignment */
ph = wav_put_bits16(ph, handle->bits);
/* bits for sample */
ph = wav_put_bits32(ph, make_tag('d', 'a', 't', 'a'));
ph = wav_put_bits32(ph, handle->len);
w = plat_write(handle->fd, hdr, WAV_HEADER_LEN);
if (!handle->has_pipe) {
ret = lseek(handle->fd, pos, SEEK_CUR);
if (ret == (off_t)-1) {
return 1;
}
}
if (w != WAV_HEADER_LEN) {
return 2;
}
handle->header_done = 1;
return 0;
}
WAV wav_open(const char *filename, WAVMode mode, WAVError *err)
{
int oflags = (mode & WAV_READ) ?O_RDONLY :O_TRUNC|O_CREAT|O_WRONLY;
int fd = -1;
WAV wav = NULL;
if (!filename || !strlen(filename)) {
WAV_SET_ERROR(err, WAV_BAD_PARAM);
} else {
fd = plat_open(filename, oflags,
S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
wav = wav_fdopen(fd, mode, err);
if (!wav) {
plat_close(fd);
} else {
wav->close_fd = 1;
}
}
return wav;
}
#define DEL_WAV(wav) do { \
plat_free((wav)); \
(wav) = NULL; \
} while (0)
WAV wav_fdopen(int fd, WAVMode mode, WAVError *err)
{
WAV wav = plat_zalloc(sizeof(struct wav_));
if (!wav) {
WAV_SET_ERROR(err, WAV_NO_MEM);
} else {
wav->fd = fd;
wav->mode = mode;
wav->close_fd = 0;
wav->has_pipe = (mode & WAV_PIPE) ?1 :0;
if (mode & WAV_READ) {
if (0 != wav_parse_header(wav, err)) {
DEL_WAV(wav);
} else {
wav->header_done = 1; /* skip write_header */
}
} else if (mode & WAV_WRITE) {
/* reserve space for header by writing a fake one */
if (!wav->has_pipe && 0 != wav_write_header(wav, 1)) {
WAV_SET_ERROR(err, wav->error);
/* only I/O error */
DEL_WAV(wav);
}
} else {
WAV_SET_ERROR(err, WAV_BAD_PARAM);
DEL_WAV(wav);
}
}
return wav;
}
#define RETURN_IF_IOERROR(err) \
if (err != 0) { \
WAV_SET_ERROR(&(handle->error), WAV_IO_ERROR); \
return -1; \
}
int wav_close(WAV handle)
{
int ret = 0;
if (!handle) {
return -1;
}
if (!handle->has_pipe && handle->mode & WAV_WRITE) {
ret = wav_write_header(handle, 1);
RETURN_IF_IOERROR(ret);
}
if (handle->close_fd) {
ret = plat_close(handle->fd);
RETURN_IF_IOERROR(ret);
}
plat_free(handle);
return 0;
}
#undef RETURN_IF_IOERROR
uint32_t wav_chunk_size(WAV handle, double fps)
{
uint32_t size = 0;
double fch;
if (!handle || !fps) {
return -1;
}
fch = handle->rate / fps;
/* bytes per audio frame */
size = (int)(fch * (handle->bits / 8) * handle->channels);
size = (size>>2)<<2; /* XXX */
return 0;
}
WAVError wav_last_error(WAV handle)
{
return (handle) ?(handle->error) :WAV_BAD_PARAM;
}
uint32_t wav_get_bitrate(WAV handle)
{
return (handle) ?(handle->bitrate) :0;
}
uint16_t wav_get_rate(WAV handle)
{
return (handle) ?(handle->rate) :0;
}
uint8_t wav_get_channels(WAV handle)
{
return (handle) ?(handle->channels) :0;
}
uint8_t wav_get_bits(WAV handle)
{
return (handle) ?(handle->bits) :0;
}
void wav_set_rate(WAV handle, uint16_t rate)
{
if (handle && handle->mode & WAV_WRITE) {
handle->rate = rate;
}
}
void wav_set_channels(WAV handle, uint8_t channels)
{
if (handle && handle->mode & WAV_WRITE) {
handle->channels = channels;
}
}
void wav_set_bits(WAV handle, uint8_t bits)
{
if (handle && handle->mode & WAV_WRITE) {
handle->bits = bits;
}
}
void wav_set_bitrate(WAV handle, uint32_t bitrate)
{
if (handle && handle->mode & WAV_WRITE) {
handle->bitrate = bitrate;
}
}
#ifdef WAV_BIG_ENDIAN
/* assume dlen % 2 == 0 */
static void bswap_buffer(void *data, size_t bytes)
{
size_t i = 0;
uint16_t *ptr = data;
for (ptr = data, i = 0; i < bytes; ptr++, i += 2) {
*ptr = bswap_16(*ptr);
}
}
#define SWAP_WRITE_CHUNK(data, len) do { \
memcpy(conv_buf, (data), (len)); \
bswap_buffer(conv_buf, (len)); \
ret = plat_write(fd, conv_buf, (len)); \
} while (0)
static ssize_t wav_bswap_fdwrite(int fd, const uint8_t *buf, size_t len)
{
uint8_t conv_buf[WAV_BUF_SIZE];
size_t blocks = len / WAV_BUF_SIZE, rest = len % WAV_BUF_SIZE, i = 0;
ssize_t ret = 0, tot = 0;
for (i = 0; i < blocks; i++) {
SWAP_WRITE_CHUNK(buf + (i * WAV_BUF_SIZE), WAV_BUF_SIZE);
if (ret != WAV_BUF_SIZE) {
break;
}
tot += ret;
}
SWAP_WRITE_CHUNK(buf + (i * WAV_BUF_SIZE), rest);
return tot + ret;
}
#undef SWAP_WRITE_CHUNK
#endif /* WAV_BIG_ENDIAN */
ssize_t wav_read_data(WAV handle, uint8_t *buffer, size_t bufsize)
{
ssize_t r = 0;
if (!handle) {
return -1;
}
if (!buffer || bufsize < 0) {
WAV_SET_ERROR(&(handle->error), WAV_BAD_PARAM);
return -1;
}
if (!(handle->mode & WAV_READ) || (bufsize % 2 != 0)) {
WAV_SET_ERROR(&(handle->error), WAV_UNSUPPORTED);
return -1;
}
r = plat_read(handle->fd, buffer, bufsize);
#ifdef WAV_BIG_ENDIAN
bswap_buffer(buffer, r);
#endif
return r;
}
ssize_t wav_write_data(WAV handle, const uint8_t *buffer, size_t bufsize)
{
ssize_t w = 0;
if (!handle) {
return -1;
}
if (!buffer || bufsize < 0) {
WAV_SET_ERROR(&(handle->error), WAV_BAD_PARAM);
return -1;
}
if (!(handle->mode & WAV_WRITE) || (bufsize % 2 != 0)) {
WAV_SET_ERROR(&(handle->error), WAV_UNSUPPORTED);
return -1;
}
if (wav_write_header(handle, 0) != 0) {
return -1;
}
#ifdef WAV_BIG_ENDIAN
w = wav_bswap_fdwrite(handle->fd, buffer, bufsize);
#else
w = plat_write(handle->fd, buffer, bufsize);
#endif
if (w == bufsize) {
handle->len += w;
}
return w;
}