diff options
Diffstat (limited to 'flow/gsl/gslloader-wav.c')
-rw-r--r-- | flow/gsl/gslloader-wav.c | 442 |
1 files changed, 442 insertions, 0 deletions
diff --git a/flow/gsl/gslloader-wav.c b/flow/gsl/gslloader-wav.c new file mode 100644 index 0000000..d0a1c1e --- /dev/null +++ b/flow/gsl/gslloader-wav.c @@ -0,0 +1,442 @@ +/* GSL - Generic Sound Layer + * Copyright (C) 1998, 2000, 2001 Tim Janik + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ +#include "gslloader.h" + +#include "gsldatahandle.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> + + +/* load routine for the RIFF/WAVE sample format + * ref.: C't 01/1993 pp. 213 + */ + +typedef guint32 DWord; +typedef guint16 Word; +#define DWORD_FROM_BE GUINT32_FROM_BE +#define DWORD_FROM_LE GUINT32_FROM_LE +#define WORD_FROM_LE GUINT16_FROM_LE + + +/* --- debugging and errors --- */ +#define WAV_DEBUG GSL_DEBUG_FUNCTION (GSL_MSG_DATA_HANDLE, "WAV") +#define WAV_MSG GSL_MESSAGE_FUNCTION (GSL_MSG_DATA_HANDLE, "WAV") + + +/* --- functions --- */ +typedef struct +{ + DWord main_chunk; /* 'RIFF', big endian as int */ + DWord file_length; /* file length */ + DWord chunk_type; /* 'WAVE', big endian as int */ +} WavHeader; +static GslErrorType +wav_read_header (gint fd, + WavHeader *header) +{ + guint n_bytes; + + memset (header, 0, sizeof (*header)); + + /* read header contents */ + n_bytes = 4 + 4 + 4; + g_assert (n_bytes == sizeof (*header)); + if (read (fd, header, n_bytes) != n_bytes) + { + WAV_DEBUG ("failed to read WavHeader"); + return GSL_ERROR_IO; + } + + /* endianess corrections */ + header->main_chunk = DWORD_FROM_BE (header->main_chunk); + header->file_length = DWORD_FROM_LE (header->file_length); + header->chunk_type = DWORD_FROM_BE (header->chunk_type); + + /* validation */ + if (header->main_chunk != ('R' << 24 | 'I' << 16 | 'F' << 8 | 'F')) + { + WAV_DEBUG ("unmatched token 'RIFF'"); + return GSL_ERROR_FORMAT_INVALID; + } + if (header->file_length < 40) + { + WAV_DEBUG ("file length (%u) too small", header->file_length); + return GSL_ERROR_FORMAT_INVALID; + } + if (header->chunk_type != ('W' << 24 | 'A' << 16 | 'V' << 8 | 'E')) + { + WAV_DEBUG ("unmatched token 'WAVE'"); + return GSL_ERROR_FORMAT_INVALID; + } + + return GSL_ERROR_NONE; +} + +typedef struct +{ + DWord sub_chunk; /* 'fmt ', big endian as int */ + DWord length; /* sub chunk length, must be 16 */ + Word format; /* 1 for PCM */ + Word n_channels; /* 1 = Mono, 2 = Stereo */ + DWord sample_freq; + DWord byte_per_second; + Word byte_per_sample; /* 1 = 8bit, 2 = 16bit */ + Word bit_per_sample; /* 8, 12 or 16 */ +} FmtHeader; +static GslErrorType +wav_read_fmt_header (gint fd, + FmtHeader *header) +{ + guint n_bytes; + + memset (header, 0, sizeof (*header)); + + /* read header contents */ + n_bytes = 4 + 4 + 2 + 2 + 4 + 4 + 2 + 2; + g_assert (n_bytes == sizeof (*header)); + if (read (fd, header, n_bytes) != n_bytes) + { + WAV_DEBUG ("failed to read FmtHeader"); + return GSL_ERROR_IO; + } + + /* endianess corrections */ + header->sub_chunk = DWORD_FROM_BE (header->sub_chunk); + header->length = DWORD_FROM_LE (header->length); + header->format = WORD_FROM_LE (header->format); + header->n_channels = WORD_FROM_LE (header->n_channels); + header->sample_freq = DWORD_FROM_LE (header->sample_freq); + header->byte_per_second = DWORD_FROM_LE (header->byte_per_second); + header->byte_per_sample = WORD_FROM_LE (header->byte_per_sample); + header->bit_per_sample = WORD_FROM_LE (header->bit_per_sample); + + /* validation */ + if (header->sub_chunk != ('f' << 24 | 'm' << 16 | 't' << 8 | ' ')) + { + WAV_DEBUG ("unmatched token 'fmt '"); + return GSL_ERROR_FORMAT_UNKNOWN; + } + if (header->format != 1 /* PCM */ || + header->n_channels > 2 || header->n_channels < 1) + { + WAV_DEBUG ("invalid format (%u) or n_channels (%u)", header->format, header->n_channels); + return GSL_ERROR_FORMAT_UNKNOWN; + } + if (header->length < 16) + { + WAV_DEBUG ("WAVE header too short (%u)", header->length); + return GSL_ERROR_FORMAT_INVALID; + } + if (header->sample_freq < 1378 || header->sample_freq > 96000) + { + WAV_DEBUG ("invalid sample_freq (%u)", header->sample_freq); + return GSL_ERROR_FORMAT_UNKNOWN; + } + if (header->byte_per_sample < 1 || header->byte_per_sample > 4 || + (header->bit_per_sample != 8 && header->bit_per_sample != 12 && header->bit_per_sample != 16)) + { + WAV_DEBUG ("invalid byte_per_sample (%u) or bit_per_sample (%u)", header->byte_per_sample, header->bit_per_sample); + return GSL_ERROR_FORMAT_UNKNOWN; + } + if (header->byte_per_second != header->sample_freq * header->byte_per_sample || + header->byte_per_sample != (header->bit_per_sample + 7) / 8 * header->n_channels) + { + WAV_DEBUG ("invalid byte_per_second (%u!=%u) or byte_per_sample (%u!=%u)", + header->byte_per_second, header->sample_freq * header->byte_per_sample, + header->byte_per_sample, (header->bit_per_sample + 7) / 8 * header->n_channels); + return GSL_ERROR_FORMAT_INVALID; + } + if (header->length > 16) + { + guint n; + + WAV_DEBUG ("WAVE header too long (%u)", header->length); + + n = header->length - 16; + while (n) + { + guint8 junk[64]; + guint l = MIN (n, 64); + + l = read (fd, junk, l); + if (l < 1 || l > n) + { + WAV_DEBUG ("failed to read FmtHeader"); + return GSL_ERROR_IO; + } + n -= l; + } + + WAV_MSG (GSL_ERROR_CONTENT_GLITCH, "skipping %u bytes of junk in WAVE header", header->length - 16); + } + + return GSL_ERROR_NONE; +} + +typedef struct +{ + DWord data_chunk; /* 'data', big endian as int */ + DWord data_length; +} DataHeader; +static GslErrorType +wav_read_data_header (gint fd, + DataHeader *header, + guint byte_alignment) +{ + guint n_bytes; + + memset (header, 0, sizeof (*header)); + + /* read header contents */ + n_bytes = 4 + 4; + g_assert (n_bytes == sizeof (*header)); + if (read (fd, header, n_bytes) != n_bytes) + { + WAV_DEBUG ("failed to read DataHeader"); + return GSL_ERROR_IO; + } + + /* endianess corrections */ + header->data_chunk = DWORD_FROM_BE (header->data_chunk); + header->data_length = DWORD_FROM_LE (header->data_length); + + /* validation */ + if (header->data_chunk != ('d' << 24 | 'a' << 16 | 't' << 8 | 'a')) + { + guchar chunk[5]; + gchar *esc; + + chunk[0] = header->data_chunk >> 24; + chunk[1] = (header->data_chunk >> 16) & 0xff; + chunk[2] = (header->data_chunk >> 8) & 0xff; + chunk[3] = header->data_chunk & 0xff; + chunk[4] = 0; + esc = g_strescape (chunk, NULL); + + /* skip chunk and retry */ + WAV_DEBUG ("ignoring sub-chunk '%s'", esc); + g_free (esc); + if (lseek (fd, header->data_length, SEEK_CUR) < 0) + { + WAV_DEBUG ("failed to seek while skipping sub-chunk"); + return GSL_ERROR_IO; + } + return wav_read_data_header (fd, header, byte_alignment); + } + if (header->data_length < 1 || header->data_length % byte_alignment != 0) + { + WAV_DEBUG ("invalid data length (%u) or alignment (%u)", + header->data_length, header->data_length % byte_alignment); + return GSL_ERROR_FORMAT_INVALID; + } + + return GSL_ERROR_NONE; +} + +typedef struct +{ + GslWaveFileInfo wfi; + gint fd; +} FileInfo; + +static GslWaveFileInfo* +wav_load_file_info (gpointer data, + const gchar *file_name, + GslErrorType *error_p) +{ + WavHeader wav_header; + FileInfo *fi; + gint fd; + + fd = open (file_name, O_RDONLY); + if (fd < 0) + { + *error_p = GSL_ERROR_OPEN_FAILED; + return NULL; + } + + *error_p = wav_read_header (fd, &wav_header); + if (*error_p) + { + close (fd); + return NULL; + } + + fi = gsl_new_struct0 (FileInfo, 1); + fi->wfi.n_waves = 1; + fi->wfi.waves = g_malloc0 (sizeof (fi->wfi.waves[0]) * fi->wfi.n_waves); + fi->wfi.waves[0].name = g_strdup (file_name); + fi->fd = fd; + + return &fi->wfi; +} + +static void +wav_free_file_info (gpointer data, + GslWaveFileInfo *file_info) +{ + FileInfo *fi = (FileInfo*) file_info; + + g_free (fi->wfi.waves[0].name); + g_free (fi->wfi.waves); + close (fi->fd); + gsl_delete_struct (FileInfo, fi); +} + +typedef struct +{ + GslWaveDsc wdsc; + GslLong data_offset; + GslLong n_values; + GslWaveFormatType format; +} WaveDsc; + +static GslWaveDsc* +wav_load_wave_dsc (gpointer data, + GslWaveFileInfo *file_info, + guint nth_wave, + GslErrorType *error_p) +{ + FileInfo *fi = (FileInfo*) file_info; + DataHeader data_header; + FmtHeader fmt_header; + WaveDsc *dsc; + GslWaveFormatType format; + GslLong data_offset, data_width; + + g_return_val_if_fail (nth_wave == 0, NULL); + + if (lseek (fi->fd, sizeof (WavHeader), SEEK_SET) != sizeof (WavHeader)) + { + WAV_DEBUG ("failed to seek to end of WavHeader"); + *error_p = GSL_ERROR_IO; + return NULL; + } + + *error_p = wav_read_fmt_header (fi->fd, &fmt_header); + if (*error_p) + return NULL; + + data_width = (fmt_header.bit_per_sample + 7) / 8; + *error_p = wav_read_data_header (fi->fd, &data_header, data_width * fmt_header.n_channels); + data_offset = lseek (fi->fd, 0, SEEK_CUR); + if (data_offset < sizeof (WavHeader) && !*error_p) + { + WAV_DEBUG ("failed to seek to start of data"); + *error_p = GSL_ERROR_IO; + } + if (*error_p) + return NULL; + + switch (fmt_header.bit_per_sample) + { + case 8: format = GSL_WAVE_FORMAT_UNSIGNED_8; break; + case 12: format = GSL_WAVE_FORMAT_SIGNED_12; break; + case 16: format = GSL_WAVE_FORMAT_SIGNED_16; break; + default: + WAV_DEBUG ("unrecognized sample width (%u)", fmt_header.bit_per_sample); + *error_p = GSL_ERROR_FORMAT_UNKNOWN; + return NULL; + } + if (0) + WAV_DEBUG ("n_channels: %d sample_freq: %d bit_width: %u", + fmt_header.n_channels, fmt_header.sample_freq, fmt_header.bit_per_sample); + + dsc = gsl_new_struct0 (WaveDsc, 1); + dsc->wdsc.name = g_strdup (fi->wfi.waves[0].name); + dsc->wdsc.n_channels = fmt_header.n_channels; + dsc->wdsc.n_chunks = 1; + dsc->wdsc.chunks = g_malloc0 (sizeof (dsc->wdsc.chunks[0]) * dsc->wdsc.n_chunks); + dsc->wdsc.chunks[0].mix_freq = fmt_header.sample_freq; + dsc->wdsc.chunks[0].osc_freq = 440.0; /* FIXME */ + dsc->data_offset = data_offset; + dsc->n_values = data_header.data_length / data_width; + dsc->format = format; + + return &dsc->wdsc; +} + +static void +wav_free_wave_dsc (gpointer data, + GslWaveDsc *wave_dsc) +{ + WaveDsc *dsc = (WaveDsc*) wave_dsc; + + g_free (dsc->wdsc.name); + g_free (dsc->wdsc.chunks); + gsl_delete_struct (WaveDsc, dsc); +} + +static GslDataHandle* +wav_create_chunk_handle (gpointer data, + GslWaveDsc *wave_dsc, + guint nth_chunk, + GslErrorType *error_p) +{ + WaveDsc *dsc = (WaveDsc*) wave_dsc; + GslDataHandle *dhandle; + + g_return_val_if_fail (nth_chunk == 0, NULL); + + dhandle = gsl_wave_handle_new (dsc->wdsc.file_info->file_name, + dsc->wdsc.n_channels, + dsc->format, G_LITTLE_ENDIAN, + dsc->data_offset, dsc->n_values); + return dhandle; +} + +void +_gsl_init_loader_wav (void) +{ + static const gchar *file_exts[] = { "wav", NULL, }; + static const gchar *mime_types[] = { "audio/wav", "audio/x-wav", NULL, }; + static const gchar *magics[] = { + ( + "0 string RIFF\n" + "8 string WAVE\n" + "12 string fmt\\s\n" /* expect "fmt " */ + "16 lelong >15\n" /* expect valid sub chunk length */ + "20 leshort =1\n" /* expect PCM format */ + ), + NULL, + }; + static GslLoader loader = { + "RIFF, WAVE audio, PCM", + file_exts, + mime_types, + magics, + 0, /* priority */ + NULL, + wav_load_file_info, + wav_free_file_info, + wav_load_wave_dsc, + wav_free_wave_dsc, + wav_create_chunk_handle, + }; + static gboolean initialized = FALSE; + + g_assert (initialized == FALSE); + initialized = TRUE; + + gsl_loader_register (&loader); +} |