alsa-utils/axfer/container-voc.c
Jaroslav Kysela ad5a1c0c88 axfer: fix the verbose compilation warnings for latest gcc
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2023-08-30 12:35:57 +02:00

835 lines
20 KiB
C

// SPDX-License-Identifier: GPL-2.0
//
// container-voc.c - a parser/builder for a container of Creative Voice File.
//
// Copyright (c) 2018 Takashi Sakamoto <o-takashi@sakamocchi.jp>
//
// Licensed under the terms of the GNU General Public License, version 2.
#include "container.h"
#include "misc.h"
// Not portable to all of UNIX platforms.
#include <endian.h>
// References:
// - http://sox.sourceforge.net/
#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;
};
struct time_const {
unsigned int frames_per_second;
uint16_t code;
};
static const struct time_const v110_time_consts[] = {
{5512, 74},
{8000, 130},
{11025, 165},
{16000, 193},
{22050, 210},
{32000, 224},
{44100, 233},
{48000, 235},
{64000, 240},
// Time constant for the upper sampling rate is not identical.
};
static const struct time_const ex_v110_time_consts[] = {
{5512, 19092},
{8000, 33536},
{11025, 42317},
{16000, 49536},
{22050, 53927},
{32000, 57536},
{44100, 59732},
{48000, 60203},
{64000, 61536},
{88200, 62634},
{96000, 62870},
{176400, 64085},
{192000, 64203},
// This support up to 192.0 kHz. The rest is for cases with 2ch.
{352800, 64811},
{384000, 64870},
};
// 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 frames[0]; // Aligned to little-endian.
};
struct block_continuous_data {
uint8_t type;
uint8_t size[3]; // Equals to the size of frames.
uint8_t frames[0]; // Aligned to little-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 frames[0]; // Aligned to little-endian.
};
// Aligned to little endian order but 24 bits field.
static uint32_t 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 int build_time_constant(unsigned int frames_per_second,
unsigned int samples_per_frame, uint16_t *code,
bool extended)
{
unsigned int i;
// 16 bits are available for this purpose.
if (extended) {
if (samples_per_frame > 2)
return -EINVAL;
frames_per_second *= samples_per_frame;
for (i = 0; i < ARRAY_SIZE(ex_v110_time_consts); ++i) {
if (ex_v110_time_consts[i].frames_per_second ==
frames_per_second)
break;
}
if (i < ARRAY_SIZE(ex_v110_time_consts) &&
frames_per_second <= 192000) {
*code = ex_v110_time_consts[i].code;
} else {
*code = 65536 - 256000000 / frames_per_second;
}
} else {
if (samples_per_frame != 1)
return -EINVAL;
for (i = 0; i < ARRAY_SIZE(v110_time_consts); ++i) {
if (v110_time_consts[i].frames_per_second ==
frames_per_second)
break;
}
// Should be within 8 bit.
if (i < ARRAY_SIZE(v110_time_consts))
*code = (uint8_t)v110_time_consts[i].code;
else
*code = 256 - 1000000 / frames_per_second;
}
return 0;
}
static unsigned int parse_time_constant(uint16_t code,
unsigned int samples_per_frame,
unsigned int *frames_per_second,
bool extended)
{
unsigned int i;
if (extended) {
if (samples_per_frame > 2)
return -EINVAL;
for (i = 0; i < ARRAY_SIZE(ex_v110_time_consts); ++i) {
if (ex_v110_time_consts[i].code == code ||
ex_v110_time_consts[i].code - 1 == code)
break;
}
if (i < ARRAY_SIZE(ex_v110_time_consts)) {
*frames_per_second =
ex_v110_time_consts[i].frames_per_second /
samples_per_frame;
} else {
*frames_per_second = 256000000 / samples_per_frame /
(65536 - code);
}
} else {
if (samples_per_frame != 1)
return -EINVAL;
for (i = 0; i < ARRAY_SIZE(v110_time_consts); ++i) {
if (v110_time_consts[i].code == code ||
v110_time_consts[i].code - 1 == code)
break;
}
if (i < ARRAY_SIZE(v110_time_consts)) {
*frames_per_second =
v110_time_consts[i].frames_per_second;
} else {
*frames_per_second = 1000000 / (256 - code);
}
}
return 0;
}
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;
uint32_t 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)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(format_maps); ++i) {
if (code_id != format_maps[i].code_id)
continue;
if (version >= format_maps[i].minimal_version)
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;
unsigned int frames_per_second;
int err;
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);
err = parse_time_constant(time_const, state->samples_per_frame,
&frames_per_second, true);
if (err < 0)
return err;
state->frames_per_second = frames_per_second;
state->extended = true;
return 0;
}
static int parse_v110_data(struct parser_state *state,
struct block_v110_data *block)
{
unsigned int time_const;
unsigned int frames_per_second;
int err;
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;
err = parse_time_constant(time_const, 1, &frames_per_second,
false);
if (err < 0)
return err;
state->frames_per_second = frames_per_second;
state->samples_per_frame = 1;
}
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;
if (cntr->eof)
return 0;
return parse_container_header(state, &header);
}
static int allocate_for_block_cache(struct container_context *cntr,
struct block_header *header, void **buf)
{
uint32_t 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_EXTENDED_V110_FORMAT)
block_size = sizeof(struct block_extended_v110_format);
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;
}
if (cntr->eof) {
free(cache);
return 0;
}
*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->type,
sizeof(header->type));
if (err < 0)
return err;
if (cntr->eof)
return 0;
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;
if (cntr->eof)
return 0;
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:
buf = NULL;
err = cache_data_block(cntr, &header, &buf);
if (err < 0)
return err;
if (buf) {
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 *byte_count)
{
struct parser_state *state = cntr->private_data;
unsigned 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.
*byte_count = state->byte_count;
return 0;
}
struct builder_state {
unsigned int version;
bool extended;
enum code_id code_id;
unsigned int samples_per_frame;
unsigned int bytes_per_sample;
};
static int write_container_header(struct container_context *cntr,
struct container_header *header)
{
struct builder_state *state = cntr->private_data;
// 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 byte_count)
{
struct builder_state *state = cntr->private_data;
block->type = BLOCK_TYPE_V120_DATA;
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;
block->code_id = htole16(state->code_id);
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;
uint16_t time_const;
int err;
block->type = BLOCK_TYPE_EXTENDED_V110_FORMAT;
build_block_data_size(block->size, 4);
// 16 bits are available for this purpose.
err = build_time_constant(frames_per_second, state->samples_per_frame,
&time_const, true);
if (err < 0)
return err;
block->time_const = htole16(time_const);
block->code_id = htole16(state->code_id);
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 byte_count)
{
struct builder_state *state = cntr->private_data;
uint16_t time_const;
int err;
block->type = BLOCK_TYPE_V110_DATA;
build_block_data_size(block->size, 2 + byte_count);
// These fields were obsoleted by extension.
err = build_time_constant(frames_per_second, 1, &time_const, false);
if (err < 0)
return err;
block->time_const = (uint8_t)time_const;
block->code_id = state->code_id;
return container_recursive_write(cntr, block, sizeof(*block));
}
static int write_data_blocks(struct container_context *cntr,
unsigned int frames_per_second,
uint64_t byte_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, byte_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, byte_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 *byte_count)
{
struct builder_state *state = cntr->private_data;
unsigned 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;
state->code_id = format_maps[i].code_id;
// Decide container version.
if (*samples_per_frame > 2)
state->version = VOC_VERSION_1_20;
else
state->version = format_maps[i].minimal_version;
if (state->version == VOC_VERSION_1_10) {
if (*samples_per_frame == 2) {
for (i = 0;
i < ARRAY_SIZE(ex_v110_time_consts); ++i) {
if (ex_v110_time_consts[i].frames_per_second ==
*frames_per_second)
break;
}
if (i == ARRAY_SIZE(ex_v110_time_consts))
state->version = VOC_VERSION_1_20;
else
state->extended = true;
} else {
for (i = 0; i < ARRAY_SIZE(v110_time_consts); ++i) {
if (v110_time_consts[i].frames_per_second ==
*frames_per_second)
break;
}
if (i == ARRAY_SIZE(v110_time_consts))
state->version = VOC_VERSION_1_20;
}
}
state->bytes_per_sample = snd_pcm_format_physical_width(*format) / 8;
state->samples_per_frame = *samples_per_frame;
return write_data_blocks(cntr, *frames_per_second, *byte_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_data_size(struct container_context *cntr, uint64_t byte_count)
{
struct builder_state *state = cntr->private_data;
off_t offset;
uint8_t size_field[3];
int err;
offset = sizeof(struct container_header) + sizeof(uint8_t);
if (state->version == VOC_VERSION_1_10 && state->extended)
offset += sizeof(struct block_extended_v110_format);
err = container_seek_offset(cntr, offset);
if (err < 0)
return err;
if (state->version == VOC_VERSION_1_10)
offset = 2;
else
offset = 12;
if (byte_count > cntr->max_size - offset)
byte_count = cntr->max_size;
else
byte_count += offset;
build_block_data_size(size_field, byte_count);
return container_recursive_write(cntr, &size_field, sizeof(size_field));
}
static int voc_builder_post_process(struct container_context *cntr,
uint64_t handled_byte_count)
{
int err;
err = write_block_terminator(cntr);
if (err < 0)
return err;
return write_data_size(cntr, handled_byte_count);
}
const struct container_parser container_parser_voc = {
.format = CONTAINER_FORMAT_VOC,
.magic = VOC_MAGIC,
.max_size = 0xffffff - // = UINT24_MAX.
sizeof(struct block_terminator),
.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 = 0xffffff - // = UINT24_MAX.
sizeof(struct block_terminator),
.ops = {
.pre_process = voc_builder_pre_process,
.post_process = voc_builder_post_process,
},
.private_size = sizeof(struct builder_state),
};