#include #include #include #include #include #include #include struct _xas_riff { int fd; size_t size; size_t sample_size, sample_rate, channels; }; static int header_write(xas_riff *riff) { xas_riff_wave_header header = { .riff = { { .id = XAS_RIFF_HEADER_MAIN_ID, .size = sizeof(xas_riff_main_chunk) + sizeof(xas_riff_wave_chunk) + riff->size }, .type = XAS_RIFF_HEADER_WAVE_ID, }, .wave = { { .id = XAS_RIFF_HEADER_WAVE_FMT_ID, .size = sizeof(xas_riff_wave_chunk) - sizeof(xas_riff_chunk) }, .type = XAS_RIFF_WAVE_DEFAULT_TYPE, .channels = riff->channels, .sample_rate = riff->sample_rate, .byte_rate = riff->channels * riff->sample_size * riff->sample_rate, .sample_size = riff->sample_size, .sample_size_bits = riff->sample_size << 3 }, { .id = XAS_RIFF_HEADER_WAVE_DATA_ID, .size = riff->size } }; if (lseek(riff->fd, 0, SEEK_SET) < 0) { goto error_io; } if (write(riff->fd, &header, sizeof(header)) < 0) { goto error_io; } if (lseek(riff->fd, 0, SEEK_END) < 0) { goto error_io; } return 0; error_io: return -1; } static xas_riff *file_new(const char *path, size_t sample_size, size_t sample_rate, size_t channels, int flags) { xas_riff *riff; if ((riff = malloc(sizeof(*riff))) == NULL) { goto error_malloc_riff; } flags |= O_CREAT; if ((riff->fd = open(path, flags, 0644)) < 0) { goto error_open; } riff->size = 0; riff->sample_size = sample_size; riff->sample_rate = sample_rate; riff->channels = channels; if (header_write(riff) < 0) { goto error_header_write; } return riff; error_header_write: close(riff->fd); error_open: free(riff); error_malloc_riff: return NULL; } static int wave_header_parse(xas_riff *riff, xas_riff_wave_header *header) { if (memcmp(header->riff.header.id, XAS_RIFF_HEADER_MAIN_ID, strlen(XAS_RIFF_HEADER_MAIN_ID)) != 0) { goto error_invalid_header_main_id; } if (header->riff.header.size < sizeof(xas_riff_main_chunk) + sizeof(xas_riff_wave_chunk)) { goto error_invalid_header_wave_chunk_size; } if (memcmp(header->riff.type, XAS_RIFF_HEADER_WAVE_ID, strlen(XAS_RIFF_HEADER_WAVE_ID)) != 0) { goto error_invalid_wave_chunk_id; } if (memcmp(header->wave.header.id, XAS_RIFF_HEADER_WAVE_FMT_ID, strlen(XAS_RIFF_HEADER_WAVE_FMT_ID)) != 0) { goto error_invalid_wave_format_id; } if (header->wave.header.size != sizeof(xas_riff_wave_chunk) - sizeof(xas_riff_chunk)) { goto error_invalid_wave_format_size; } if (header->wave.type != XAS_RIFF_WAVE_DEFAULT_TYPE) { goto error_invalid_wave_format_type; } switch (header->wave.channels) { case XAS_AUDIO_STREAM_MONO: case XAS_AUDIO_STREAM_STEREO: break; default: goto error_invalid_wave_channels; } if (header->wave.byte_rate != header->wave.channels * header->wave.sample_size * header->wave.sample_rate) { goto error_invalid_wave_byte_rate; } if (header->wave.sample_size_bits != header->wave.sample_size << 3) { goto error_invalid_wave_sample_size_bits; } if (memcmp(header->data.id, XAS_RIFF_HEADER_WAVE_DATA_ID, strlen(XAS_RIFF_HEADER_WAVE_DATA_ID)) != 0) { goto error_invalid_wave_data_format_id; } riff->sample_size = header->wave.sample_size; riff->sample_rate = header->wave.sample_rate; riff->channels = header->wave.channels; return 0; error_invalid_wave_data_format_id: error_invalid_wave_sample_size_bits: error_invalid_wave_byte_rate: error_invalid_wave_channels: error_invalid_wave_format_type: error_invalid_wave_format_size: error_invalid_wave_format_id: error_invalid_wave_chunk_id: error_invalid_header_wave_chunk_size: error_invalid_header_main_id: return -1; } static xas_riff *file_open(const char *path, int flags) { xas_riff *riff; xas_riff_wave_header header; ssize_t readlen; if ((riff = malloc(sizeof(*riff))) == NULL) { goto error_malloc_riff; } flags &= ~(O_CREAT | O_TRUNC); if ((riff->fd = open(path, flags)) < 0) { goto error_open; } riff->size = 0; riff->sample_size = 0; riff->sample_rate = 0; riff->channels = 0; if (lseek(riff->fd, 0, SEEK_SET) < 0) { goto error_lseek; } if ((readlen = read(riff->fd, &header, sizeof(header))) < 0) { goto error_read; } if (readlen != sizeof(header)) { errno= EINVAL; goto error_wave_header_short; } if (wave_header_parse(riff, &header) < 0) { goto error_wave_header_parse; } return riff; error_wave_header_parse: error_wave_header_short: error_read: error_lseek: close(riff->fd); error_open: free(riff); error_malloc_riff: return NULL; } static void file_close(xas_riff *riff, xas_audio_stream *stream) { if (lseek(riff->fd, 0, SEEK_SET) < 0) { goto error_io; } if (header_write(riff) < 0) { goto error_header_write; } (void)close(riff->fd); free(riff); error_header_write: error_io: return; } static int audio_drain(xas_riff *riff, void *samples, size_t count, xas_audio_stream *stream) { size_t len = count * stream->sample_size * stream->channels; ssize_t wrlen; if ((wrlen = write(riff->fd, samples, len)) < 0) { goto error_write; } riff->size += wrlen; return wrlen / stream->sample_size / stream->channels; error_write: return -1; } static ssize_t audio_fill(xas_riff *riff, void *samples, size_t count, xas_audio_stream *stream) { size_t len = count * stream->sample_size * stream->channels; ssize_t rdlen; if ((rdlen = read(riff->fd, samples, len)) < 0) { goto error_read; } return rdlen / stream->sample_size / stream->channels; error_read: return -1; } xas_audio_stream *xas_riff_file_new(const char *path, size_t sample_size, size_t sample_rate, size_t channels, int flags) { xas_audio_stream *stream; xas_riff *riff; if ((riff = file_new(path, sample_size, sample_rate, channels, flags)) == NULL) { goto error_file_new; } if (flags & (O_RDWR | O_WRONLY)) { if ((stream = xas_audio_stream_new_sink((xas_audio_drain)audio_drain, (xas_audio_cleanup)file_close, riff, sample_size, sample_rate, channels, 4096)) == NULL) { goto error_audio_stream_new_sink; } } else { if ((stream = xas_audio_stream_new_source((xas_audio_fill)audio_fill, (xas_audio_cleanup)file_close, riff, sample_size, sample_rate, channels, 4096)) == NULL) { goto error_audio_stream_new_sink; } } return stream; error_audio_stream_new_sink: file_close(riff, NULL); error_file_new: return NULL; } xas_audio_stream *xas_riff_file_open(const char *path, int flags) { xas_audio_stream *stream; xas_riff *riff; if ((riff = file_open(path, flags)) == NULL) { goto error_file_open; } if (flags & (O_RDWR | O_WRONLY)) { if ((stream = xas_audio_stream_new_sink((xas_audio_drain)audio_drain, (xas_audio_cleanup)file_close, riff, riff->sample_size, riff->sample_rate, riff->channels, 4096)) == NULL) { goto error_audio_stream_new_sink; } } else { if ((stream = xas_audio_stream_new_source((xas_audio_fill)audio_fill, (xas_audio_cleanup)file_close, riff, riff->sample_size, riff->sample_rate, riff->channels, 4096)) == NULL) { goto error_audio_stream_new_sink; } } return stream; error_audio_stream_new_sink: file_close(riff, NULL); error_file_open: return NULL; }