mirror of
https://github.com/alsa-project/alsa-utils
synced 2024-11-08 19:35:44 +01:00
ad5a1c0c88
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
835 lines
20 KiB
C
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),
|
|
};
|