This commit adds support for data of Creative Tech. voice format. In this data format, each of field can have value in little endian-ness. A file can include multiple data streams in each block. However for simplicity, this commit supports the first block for data stream. --- aplay/Makefile.am | 3 +- aplay/container-voc.c | 736 ++++++++++++++++++++++++++++++++++++++++++++++++++ aplay/container.c | 4 + aplay/container.h | 4 + 4 files changed, 746 insertions(+), 1 deletion(-) create mode 100644 aplay/container-voc.c
diff --git a/aplay/Makefile.am b/aplay/Makefile.am index 8e4a31f..ef70bcb 100644 --- a/aplay/Makefile.am +++ b/aplay/Makefile.am @@ -15,7 +15,8 @@ aplay_SOURCES = \ container.h \ container.c \ container-riff-wave.c \ - container-au.c + container-au.c \ + container-voc.c
EXTRA_DIST = aplay.1 arecord.1 EXTRA_CLEAN = arecord diff --git a/aplay/container-voc.c b/aplay/container-voc.c new file mode 100644 index 0000000..d9491cd --- /dev/null +++ b/aplay/container-voc.c @@ -0,0 +1,736 @@ +/* + * container-voc.c - a parser/builder for a container of Creative Voice File. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "container.h" + +/* + * References: + * - 'Creative Voice (VOC) file format' documentation in libRealSpace + */ + +#define VOC_MAGIC "Creative Voice File\x1A" +#define VOC_VERSION_1_10 0x010a +#define VOC_VERSION_1_20 0x0114 + +enum block_type { + BLOCK_TYPE_TERMINATOR = 0x00, + BLOCK_TYPE_V110_DATA = 0x01, + BLOCK_TYPE_CONTINUOUS_DATA = 0x02, + BLOCK_TYPE_SILENCE = 0x03, + BLOCK_TYPE_MARKER = 0x04, + BLOCK_TYPE_STRING = 0x05, + BLOCK_TYPE_REPEAT_START = 0x06, + BLOCK_TYPE_REPEAT_END = 0x07, + BLOCK_TYPE_EXTENDED_V110_FORMAT = 0x08, + BLOCK_TYPE_V120_DATA = 0x09, +}; + +enum code_id { + /* Version 1.10. */ + CODE_ID_GENERIC_MBLA_U8 = 0x00, + CODE_ID_CREATIVE_ADPCM_8BIT_TO_4BIT_LE = 0x01, + CODE_ID_CREATIVE_ADPCM_8BIT_TO_3BIT_LE = 0x02, + CODE_ID_CREATIVE_ADPCM_8BIT_TO_2BIT_LE = 0x03, + /* Version 1.20. */ + CODE_ID_GENERIC_MBLA_S16_LE = 0x04, + CODE_ID_CCIT_A_LAW_LE = 0x06, + CODE_ID_CCIT_MU_LAW_LE = 0x07, + CODE_ID_CREATIVE_ADPCM_16BIT_TO_4BIT_LE = 0x2000, +}; + +struct format_map { + unsigned int minimal_version; + enum code_id code_id; + snd_pcm_format_t format; +}; + +static const struct format_map format_maps[] = { + {VOC_VERSION_1_10, CODE_ID_GENERIC_MBLA_U8, SND_PCM_FORMAT_U8}, + {VOC_VERSION_1_20, CODE_ID_GENERIC_MBLA_S16_LE, SND_PCM_FORMAT_S16_LE}, + {VOC_VERSION_1_20, CODE_ID_CCIT_A_LAW_LE, SND_PCM_FORMAT_A_LAW}, + {VOC_VERSION_1_20, CODE_ID_CCIT_MU_LAW_LE, SND_PCM_FORMAT_MU_LAW}, + /* The other formats are not supported by ALSA. */ +}; + +struct container_header { + uint8_t magic[20]; + uint16_t hdr_size; + uint16_t version; + uint16_t version_compr; +}; + +/* A format for data blocks except for terminator type. */ +struct block_header { + uint8_t type; + uint8_t size[3]; + + uint8_t data[0]; +}; + +/* Data block for terminator type has an exceptional format. */ +struct block_terminator { + uint8_t type; +}; + +/* + * v1.10 format: + * - monaural. + * - frames_per_second = 1,000,000 / (256 - time_const) + */ +struct block_v110_data { + uint8_t type; + uint8_t size[3]; /* Equals to (2 + the size of frames). */ + + uint8_t time_const; + uint8_t code_id; + uint8_t samples[0]; /* Aligned to big-endian. */ +}; + +struct block_continuous_data { + uint8_t type; + uint8_t size[3]; /* Equals to the size of frames. */ + + uint8_t samples[0]; /* Aligned to big-endian. */ +}; + +/* + * v1.10 format: + * - monaural. + * - frames_per_second = 1,000,000 / (256 - time_const). + */ +struct block_silence { + uint8_t type; + uint8_t size[3]; /* Equals to 3. */ + + uint16_t frame_count; + uint8_t time_const; +}; + +struct block_marker { + uint8_t type; + uint8_t size[3]; /* Equals to 2. */ + + uint16_t mark; +}; + +struct block_string { + uint8_t type; + uint8_t size[3]; /* Equals to the length of string with 0x00. */ + + uint8_t chars[0]; +}; + +struct block_repeat_start { + uint8_t type; + uint8_t size[3]; /* Equals to 2. */ + + uint16_t count; +}; + +struct block_repeat_end { + uint8_t type; + uint8_t size[3]; /* Equals to 0. */ +}; + +/* + * Extended v1.10 format: + * - manaural/stereo. + * - frames_per_second = + * 256,000,000 / (samples_per_frame * (65536 - time_const)). + * - Appear just before v110_data block. + */ +struct block_extended_v110_format { + uint8_t type; + uint8_t size[3]; /* Equals to 4. */ + + uint16_t time_const; + uint8_t code_id; + uint8_t ch_mode; /* 0 is monaural, 1 is stereo. */ +}; + +/* + * v1.20 format: + * - monaural/stereo. + * - 8/16 bits_per_sample. + * - time_const is not used. + * - code_id is extended. + */ +struct block_v120_format { + uint8_t type; + uint8_t size[3]; /* Equals to (12 + ). */ + + uint32_t frames_per_second; + uint8_t bits_per_sample; + uint8_t samples_per_frame; + uint16_t code_id; + uint8_t reserved[4]; + + uint8_t samples[0]; +}; + +/* Aligned to little endian order but 24 bits field. */ +static unsigned int parse_block_data_size(uint8_t fields[3]) +{ + return (fields[2] << 16) | (fields[1] << 8) | fields[0]; +} + +static void build_block_data_size(uint8_t fields[3], unsigned int size) +{ + fields[0] = (size & 0x0000ff); + fields[1] = (size & 0x00ff00) >> 8; + fields[2] = (size & 0xff0000) >> 16; +} + +static unsigned int build_time_constant(unsigned int frames_per_second, + unsigned int samples_per_frame, + bool extended) +{ + unsigned int samples_per_second = samples_per_frame * frames_per_second; + + /* 16 bits are available for this purpose. */ + if (extended) + return 65536 - 256000000 / samples_per_second; + + /* Should be within 8 bit. */ + return 256 - 1000000 / samples_per_second; +} + +static unsigned int parse_time_constant(unsigned int time_const, + unsigned int samples_per_frame, + bool extended) +{ + if (extended) + return 256000000 / (samples_per_frame * (65536 - time_const)); + + return 1000000 / (256 - time_const); +} + +struct parser_state { + unsigned int version; + bool extended; + + unsigned int frames_per_second; + unsigned int samples_per_frame; + unsigned int bytes_per_sample; + enum code_id code_id; + unsigned int byte_count; +}; + +static int parse_container_header(struct parser_state *state, + struct container_header *header) +{ + uint16_t hdr_size; + uint16_t version; + uint16_t version_compr; + + hdr_size = le16toh(header->hdr_size); + version = le16toh(header->version); + version_compr = le16toh(header->version_compr); + + if (!memcmp(header->magic, VOC_MAGIC, sizeof(header->magic))) + return -EIO; + + if (hdr_size != sizeof(*header)) + return -EIO; + + if (version_compr != 0x1234 - ~version) + return -EIO; + + if (version != VOC_VERSION_1_10 || version != VOC_VERSION_1_20) + return -EIO; + + state->version = version; + + return 0; +} + +static bool check_code_id(uint8_t code_id, unsigned int version) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(format_maps); ++i) { + if (format_maps[i].minimal_version > version) + continue; + if (format_maps[i].code_id == code_id) + return true; + } + + return false; +} + +static int parse_v120_format_block(struct parser_state *state, + struct block_v120_format *block) +{ + state->frames_per_second = le32toh(block->frames_per_second); + state->bytes_per_sample = block->bits_per_sample / 8; + state->samples_per_frame = block->samples_per_frame; + state->code_id = le16toh(block->code_id); + state->byte_count = parse_block_data_size(block->size) - 12; + + if (check_code_id(state->code_id, VOC_VERSION_1_20)) + return -EIO; + + return 0; +} + +static int parse_extended_v110_format(struct parser_state *state, + struct block_extended_v110_format *block) +{ + unsigned int time_const; + + state->code_id = block->code_id; + if (check_code_id(state->code_id, VOC_VERSION_1_10)) + return -EIO; + + if (block->ch_mode == 0) + state->samples_per_frame = 1; + else if (block->ch_mode == 1) + state->samples_per_frame = 2; + else + return -EIO; + + time_const = le16toh(block->time_const); + state->frames_per_second = + parse_time_constant(time_const, state->samples_per_frame, true); + if (state->frames_per_second == 0) + return -EIO; + + state->extended = true; + + return 0; +} + +static int parse_v110_data(struct parser_state *state, + struct block_v110_data *block) +{ + unsigned int time_const; + + if (!state->extended) { + state->code_id = block->code_id; + if (check_code_id(state->code_id, VOC_VERSION_1_10)) + return -EIO; + + time_const = block->time_const; + state->frames_per_second = + parse_time_constant(time_const, 1, false); + if (state->frames_per_second == 0) + return -EIO; + } + + state->bytes_per_sample = 1; + state->byte_count = parse_block_data_size(block->size) - 2; + + return 0; +} + +static int detect_container_version(struct container_context *cntr) +{ + struct parser_state *state = cntr->private_data; + struct container_header header = {0}; + int err; + + /* 4 bytes were alread read to detect container type. */ + memcpy(&header.magic, cntr->magic, sizeof(cntr->magic)); + err = container_recursive_read(cntr, + (char *)&header + sizeof(cntr->magic), + sizeof(header) - sizeof(cntr->magic)); + if (err < 0) + return err; + + return parse_container_header(state, &header); +} + +static int allocate_for_block_cache(struct container_context *cntr, + struct block_header *header, void **buf) +{ + unsigned int block_size; + char *cache; + int err; + + if (header->type == BLOCK_TYPE_V110_DATA) + block_size = sizeof(struct block_v110_data); + else if (header->type == BLOCK_TYPE_CONTINUOUS_DATA) + block_size = sizeof(struct block_continuous_data); + else if (header->type == BLOCK_TYPE_V120_DATA) + block_size = sizeof(struct block_v120_format); + else + block_size = parse_block_data_size(header->size); + + cache = malloc(block_size); + if (cache == NULL) + return -ENOMEM; + memset(cache, 0, block_size); + + memcpy(cache, header, sizeof(*header)); + err = container_recursive_read(cntr, cache + sizeof(*header), + block_size - sizeof(*header)); + if (err < 0) { + free(cache); + return err; + } + + *buf = cache; + + return 0; +} + +static int cache_data_block(struct container_context *cntr, + struct block_header *header, void **buf) +{ + int err; + + /* Check type of this block. */ + err = container_recursive_read(cntr, header, sizeof(*header)); + if (err < 0) + return err; + if (header->type > BLOCK_TYPE_V120_DATA) + return -EIO; + if (header->type == BLOCK_TYPE_TERMINATOR) + return 0; + + /* Check size of this block. If the block includes a batch of data, */ + err = container_recursive_read(cntr, &header->size, + sizeof(header->size)); + if (err < 0) + return err; + + return allocate_for_block_cache(cntr, header, buf); +} + +static int detect_format_block(struct container_context *cntr) +{ + struct parser_state *state = cntr->private_data; + struct block_header header; + void *buf; + int err; + +again: + err = cache_data_block(cntr, &header, &buf); + if (err < 0) + return err; + + if (header.type == BLOCK_TYPE_EXTENDED_V110_FORMAT) { + err = parse_extended_v110_format(state, buf); + } else if (header.type == BLOCK_TYPE_V120_DATA) { + err = parse_v120_format_block(state, buf); + } else if (header.type == BLOCK_TYPE_V110_DATA) { + err = parse_v110_data(state, buf); + } else { + free(buf); + goto again; + } + + free(buf); + + if (err < 0) { + return err; + } + + /* Expect to detect block_v110_data. */ + if (header.type == BLOCK_TYPE_EXTENDED_V110_FORMAT) + goto again; + + return 0; +} + +static int voc_parser_pre_process(struct container_context *cntr, + snd_pcm_format_t *format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + uint64_t *frame_count) +{ + struct parser_state *state = cntr->private_data; + int i; + int err; + + err = detect_container_version(cntr); + if (err < 0) + return err; + + err = detect_format_block(cntr); + if (err < 0) + return err; + + for (i = 0; i < ARRAY_SIZE(format_maps); ++i) { + if (format_maps[i].code_id == state->code_id) + break; + } + if (i == ARRAY_SIZE(format_maps)) + return -EINVAL; + + *format = format_maps[i].format; + *samples_per_frame = state->samples_per_frame; + *frames_per_second = state->frames_per_second; + + /* This program handles PCM frames in this data block only. */ + *frame_count = state->byte_count / snd_pcm_format_width(*format) / + state->samples_per_frame; + + return 0; +} + +struct builder_state { + unsigned int version; + bool extended; + + unsigned int samples_per_frame; + unsigned int bytes_per_sample; +}; + +static int write_container_header(struct container_context *cntr, void *buf) +{ + struct builder_state *state = cntr->private_data; + struct container_header *header = buf; + + /* Process container header. */ + memcpy(header->magic, VOC_MAGIC, sizeof(header->magic)); + header->hdr_size = htole16(sizeof(header)); + header->version = htole16(state->version); + header->version_compr = htole16(0x1234 + ~state->version); + + return container_recursive_write(cntr, &header, sizeof(*header)); +} + +static int write_v120_format_block(struct container_context *cntr, + struct block_v120_format *block, + unsigned int frames_per_second, + uint64_t frame_count) +{ + struct builder_state *state = cntr->private_data; + uint64_t byte_count; + + block->type = BLOCK_TYPE_V120_DATA; + byte_count = frame_count * state->samples_per_frame * + state->bytes_per_sample; + build_block_data_size(block->size, 12 + byte_count); + + block->frames_per_second = htole32(frames_per_second); + block->bits_per_sample = state->bytes_per_sample * 8; + block->samples_per_frame = state->samples_per_frame; + if (state->bytes_per_sample == 1) + block->code_id = htole16(CODE_ID_GENERIC_MBLA_U8); + else + block->code_id = htole16(CODE_ID_GENERIC_MBLA_S16_LE); + + return container_recursive_write(cntr, block, sizeof(*block)); +} + +static int write_extended_v110_format_block(struct container_context *cntr, + unsigned int frames_per_second, + struct block_extended_v110_format *block) +{ + struct builder_state *state = cntr->private_data; + unsigned int time_const; + + block->type = BLOCK_TYPE_EXTENDED_V110_FORMAT; + build_block_data_size(block->size, 4); + + /* 16 bits are available for this purpose. */ + time_const = build_time_constant(frames_per_second, + state->samples_per_frame, true); + block->time_const = htobe16(time_const); + block->code_id = CODE_ID_GENERIC_MBLA_U8; + + if (state->samples_per_frame == 1) + block->ch_mode = 0; + else + block->ch_mode = 1; + + return container_recursive_write(cntr, block, sizeof(*block)); +} + +static int write_v110_format_block(struct container_context *cntr, + struct block_v110_data *block, + unsigned int frames_per_second, + uint64_t frame_count) +{ + struct builder_state *state = cntr->private_data; + uint64_t byte_count; + + block->type = BLOCK_TYPE_V110_DATA; + byte_count = state->bytes_per_sample * state->samples_per_frame * + frame_count; + build_block_data_size(block->size, 2 + byte_count); + + /* This field was obsoleted by extension. */ + block->time_const = build_time_constant(frames_per_second, 1, false); + block->code_id = CODE_ID_GENERIC_MBLA_U8; + + return container_recursive_write(cntr, block, sizeof(*block)); +} + +static int write_data_blocks(struct container_context *cntr, + unsigned int frames_per_second, + uint64_t frame_count) +{ + union { + struct container_header header; + struct block_v110_data v110_data; + struct block_extended_v110_format extended_v110_format; + struct block_v120_format v120_format; + } buf = {0}; + struct builder_state *state = cntr->private_data; + int err; + + err = write_container_header(cntr, &buf.header); + if (err < 0) + return err; + + if (state->version == VOC_VERSION_1_20) { + err = write_v120_format_block(cntr, &buf.v120_format, + frames_per_second, frame_count); + } else { + if (state->extended) { + err = write_extended_v110_format_block(cntr, + frames_per_second, + &buf.extended_v110_format); + if (err < 0) + return err; + } + err = write_v110_format_block(cntr, &buf.v110_data, + frames_per_second, frame_count); + } + + return err; +} + +static int voc_builder_pre_process(struct container_context *cntr, + snd_pcm_format_t *format, + unsigned int *samples_per_frame, + unsigned int *frames_per_second, + uint64_t *frame_count) +{ + struct builder_state *state = cntr->private_data; + bool extended = false; + int i; + + /* Validate parameters. */ + for (i = 0; i < ARRAY_SIZE(format_maps); ++i) { + if (format_maps[i].format == *format) + break; + } + if (i == ARRAY_SIZE(format_maps)) + return -EINVAL; + + /* Decide container version. */ + if (format_maps[i].minimal_version == VOC_VERSION_1_10) { + if (*samples_per_frame > 2) + return -EINVAL; + extended = !!(*samples_per_frame == 2); + } + + state->bytes_per_sample = snd_pcm_format_physical_width(*format) / 8; + state->samples_per_frame = *samples_per_frame; + state->version = format_maps[i].minimal_version; + state->extended = extended; + + return write_data_blocks(cntr, *frames_per_second, *frame_count); +} + +static int write_block_terminator(struct container_context *cntr) +{ + struct block_terminator block = {0}; + + block.type = BLOCK_TYPE_TERMINATOR; + return container_recursive_write(cntr, &block, sizeof(block)); +} + +static int write_size_to_v120_format_block(struct container_context *cntr, + uint64_t actual_frame_count) +{ + struct builder_state *state = cntr->private_data; + off64_t offset; + uint64_t actual_byte_count; + uint8_t size_field[3]; + int err; + + offset = sizeof(struct container_header) + sizeof(uint8_t); + err = container_seek_offset(cntr, offset); + if (err < 0) + return err; + + /* Don't forget to add 12 bytes for this block. */ + actual_byte_count = 12 + actual_frame_count * + state->samples_per_frame * state->bytes_per_sample; + size_field[0] = (actual_byte_count & 0x0000ff); + size_field[1] = (actual_byte_count & 0x00ff00) >> 8; + size_field[2] = (actual_byte_count & 0xff0000) >> 16; + + return container_recursive_write(cntr, &size_field, sizeof(size_field)); +} + +static int write_size_to_v110_format_block(struct container_context *cntr, + uint64_t actual_frame_count) +{ + struct builder_state *state = cntr->private_data; + off64_t offset; + uint64_t actual_byte_count; + uint8_t size_field[3]; + int err; + + offset = sizeof(struct container_header) + sizeof(uint8_t); + if (state->extended) + offset += sizeof(struct block_extended_v110_format); + err = container_seek_offset(cntr, offset); + if (err < 0) + return err; + + /* Don't forget to add 2 bytes for this block. */ + actual_byte_count = 2 + actual_frame_count; + size_field[0] = (actual_byte_count & 0x000000); + size_field[1] = (actual_byte_count & 0x000000) >> 8; + size_field[2] = (actual_byte_count & 0x000000) >> 16; + + return container_recursive_write(cntr, &size_field, sizeof(size_field)); +} + +static int write_size_to_format_block(struct container_context *cntr, + uint64_t actual_frame_count) +{ + struct builder_state *state = cntr->private_data; + int err; + + if (state->version == VOC_VERSION_1_20) + err = write_size_to_v120_format_block(cntr, actual_frame_count); + else + err = write_size_to_v110_format_block(cntr, actual_frame_count); + + return err; +} + +static int voc_builder_post_process(struct container_context *cntr, + uint64_t actual_frame_count) +{ + int err; + + err = write_block_terminator(cntr); + if (err < 0) + return err; + + return write_size_to_format_block(cntr, actual_frame_count); +} + +const struct container_parser container_parser_voc = { + .format = CONTAINER_FORMAT_VOC, + .magic = VOC_MAGIC, + .max_size = 16000000ll, + .ops = { + .pre_process = voc_parser_pre_process, + }, + .private_size = sizeof(struct parser_state), +}; + +const struct container_builder container_builder_voc = { + .format = CONTAINER_FORMAT_VOC, + .max_size = 16000000ll, + .ops = { + .pre_process = voc_builder_pre_process, + .post_process = voc_builder_post_process, + }, + .private_size = sizeof(struct builder_state), +}; diff --git a/aplay/container.c b/aplay/container.c index 6214bbf..1cd7ada 100644 --- a/aplay/container.c +++ b/aplay/container.c @@ -22,11 +22,13 @@ static const char *const cntr_type_labels[] = { static const char *const cntr_format_labels[] = { [CONTAINER_FORMAT_RIFF_WAVE] = "riff/wave", [CONTAINER_FORMAT_AU] = "au", + [CONTAINER_FORMAT_VOC] = "voc", };
static const char *const suffixes[] = { [CONTAINER_FORMAT_RIFF_WAVE] = ".wav", [CONTAINER_FORMAT_AU] = ".au", + [CONTAINER_FORMAT_VOC] = ".voc", };
@@ -151,6 +153,7 @@ int container_parser_init(struct container_context *cntr, const struct container_parser *parsers[] = { [CONTAINER_FORMAT_RIFF_WAVE] = &container_parser_riff_wave, [CONTAINER_FORMAT_AU] = &container_parser_au, + [CONTAINER_FORMAT_VOC] = &container_parser_voc, }; const struct container_parser *parser; unsigned int size; @@ -220,6 +223,7 @@ int container_builder_init(struct container_context *cntr, const struct container_builder *builders[] = { [CONTAINER_FORMAT_RIFF_WAVE] = &container_builder_riff_wave, [CONTAINER_FORMAT_AU] = &container_builder_au, + [CONTAINER_FORMAT_VOC] = &container_builder_voc, }; const struct container_builder *builder; int err; diff --git a/aplay/container.h b/aplay/container.h index 73029f4..994ff9b 100644 --- a/aplay/container.h +++ b/aplay/container.h @@ -32,6 +32,7 @@ enum container_type { enum container_format { CONTAINER_FORMAT_RIFF_WAVE = 0, CONTAINER_FORMAT_AU, + CONTAINER_FORMAT_VOC, CONTAINER_FORMAT_COUNT, };
@@ -119,4 +120,7 @@ extern const struct container_builder container_builder_riff_wave; extern const struct container_parser container_parser_au; extern const struct container_builder container_builder_au;
+extern const struct container_parser container_parser_voc; +extern const struct container_builder container_builder_voc; + #endif