alsa-utils/axfer/container.c
Takashi Sakamoto 4ab7510f3a axfer: add support for a container of Creative Tech. voice format
This commit adds support for data of Creative Tech. voice format. In this
data format, values in each of field are represented in little-endian byte
order and available formats of data sample are restricted in little-endian
byte order.

In version 1.10 of this format, sampling rate is represented with
reciprocal number of the rate, thus we cannot calculate original sampling
rate precisely just from its header. For example at 44.1kHz, file header
includes 233 (=256-1,000,000/44,100), but we cannot recover the value just
from the code (43478.2...). For my convenience, this commit adds a
pre-computed table and lookup major rates from the table.

Additionally, this format can includes several blocks with different
sample format. When handling this type of file, we need to start/stop
substream for each of the block, while this brings complicated code.
This type of format is enough ancient and presently quite minor. This
commit takes a compromise and handles a first sample block only.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2018-11-13 12:04:23 +01:00

433 lines
10 KiB
C

// SPDX-License-Identifier: GPL-2.0
//
// container.c - an interface of parser/builder for formatted files.
//
// 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"
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
static const char *const cntr_type_labels[] = {
[CONTAINER_TYPE_PARSER] = "parser",
[CONTAINER_TYPE_BUILDER] = "builder",
};
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",
};
const char *const container_suffix_from_format(enum container_format format)
{
return suffixes[format];
}
int container_recursive_read(struct container_context *cntr, void *buf,
unsigned int byte_count)
{
char *dst = buf;
ssize_t result;
size_t consumed = 0;
while (consumed < byte_count && !cntr->interrupted) {
result = read(cntr->fd, dst + consumed, byte_count - consumed);
if (result < 0) {
// This descriptor was configured with non-blocking
// mode. EINTR is not cought when get any interrupts.
if (cntr->interrupted)
return -EINTR;
if (errno == EAGAIN)
continue;
return -errno;
}
// Reach EOF.
if (result == 0) {
cntr->eof = true;
return 0;
}
consumed += result;
}
return 0;
}
int container_recursive_write(struct container_context *cntr, void *buf,
unsigned int byte_count)
{
char *src = buf;
ssize_t result;
size_t consumed = 0;
while (consumed < byte_count && !cntr->interrupted) {
result = write(cntr->fd, src + consumed, byte_count - consumed);
if (result < 0) {
// This descriptor was configured with non-blocking
// mode. EINTR is not cought when get any interrupts.
if (cntr->interrupted)
return -EINTR;
if (errno == EAGAIN)
continue;
return -errno;
}
consumed += result;
}
return 0;
}
enum container_format container_format_from_path(const char *path)
{
const char *suffix;
const char *pos;
int i;
for (i = 0; i < ARRAY_SIZE(suffixes); ++i) {
suffix = suffixes[i];
// Check last part of the string.
pos = path + strlen(path) - strlen(suffix);
if (!strcmp(pos, suffix))
return i;
}
// Unsupported.
return CONTAINER_FORMAT_COUNT;
}
int container_seek_offset(struct container_context *cntr, off64_t offset)
{
off64_t pos;
pos = lseek64(cntr->fd, offset, SEEK_SET);
if (pos < 0)
return -errno;
if (pos != offset)
return -EIO;
return 0;
}
// To avoid blocking execution at system call iteration after receiving UNIX
// signals.
static int set_nonblock_flag(int fd)
{
int flags;
flags = fcntl(fd, F_GETFL);
if (flags < 0)
return -errno;
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) < 0)
return -errno;
return 0;
}
int container_parser_init(struct container_context *cntr,
const char *const path, unsigned int verbose)
{
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;
int i;
int err;
assert(cntr);
assert(path);
assert(path[0] != '\0');
// Detect forgotten to destruct.
assert(cntr->fd == 0);
assert(cntr->private_data == NULL);
memset(cntr, 0, sizeof(*cntr));
// Open a target descriptor.
if (!strcmp(path, "-")) {
cntr->fd = fileno(stdin);
err = set_nonblock_flag(cntr->fd);
if (err < 0)
return err;
cntr->stdio = true;
} else {
cntr->fd = open(path, O_RDONLY | O_NONBLOCK);
if (cntr->fd < 0)
return -errno;
}
// 4 bytes are enough to detect supported containers.
err = container_recursive_read(cntr, cntr->magic, sizeof(cntr->magic));
if (err < 0)
return err;
for (i = 0; i < ARRAY_SIZE(parsers); ++i) {
parser = parsers[i];
size = strlen(parser->magic);
if (size > 4)
size = 4;
if (!strncmp(cntr->magic, parser->magic, size))
break;
}
// Don't forget that the first 4 bytes were already read for magic
// bytes.
cntr->magic_handled = false;
// Unless detected, use raw container.
if (i == ARRAY_SIZE(parsers))
return -EINVAL;
// Allocate private data for the parser.
if (parser->private_size > 0) {
cntr->private_data = malloc(parser->private_size);
if (cntr->private_data == NULL)
return -ENOMEM;
memset(cntr->private_data, 0, parser->private_size);
}
cntr->type = CONTAINER_TYPE_PARSER;
cntr->process_bytes = container_recursive_read;
cntr->format = parser->format;
cntr->ops = &parser->ops;
cntr->max_size = parser->max_size;
cntr->verbose = verbose;
return 0;
}
int container_builder_init(struct container_context *cntr,
const char *const path, enum container_format format,
unsigned int verbose)
{
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;
assert(cntr);
assert(path);
assert(path[0] != '\0');
// Detect forgotten to destruct.
assert(cntr->fd == 0);
assert(cntr->private_data == NULL);
memset(cntr, 0, sizeof(*cntr));
// Open a target descriptor.
if (path == NULL || *path == '\0')
return -EINVAL;
if (!strcmp(path, "-")) {
cntr->fd = fileno(stdout);
err = set_nonblock_flag(cntr->fd);
if (err < 0)
return err;
cntr->stdio = true;
} else {
cntr->fd = open(path, O_RDWR | O_NONBLOCK | O_CREAT | O_TRUNC,
0644);
if (cntr->fd < 0)
return -errno;
}
builder = builders[format];
// Allocate private data for the builder.
if (builder->private_size > 0) {
cntr->private_data = malloc(builder->private_size);
if (cntr->private_data == NULL)
return -ENOMEM;
memset(cntr->private_data, 0, builder->private_size);
}
cntr->type = CONTAINER_TYPE_BUILDER;
cntr->process_bytes = container_recursive_write;
cntr->format = builder->format;
cntr->ops = &builder->ops;
cntr->max_size = builder->max_size;
cntr->verbose = verbose;
return 0;
}
int container_context_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)
{
uint64_t byte_count;
unsigned int bytes_per_frame;
int err;
assert(cntr);
assert(format);
assert(samples_per_frame);
assert(frames_per_second);
assert(frame_count);
if (cntr->type == CONTAINER_TYPE_BUILDER)
byte_count = cntr->max_size;
if (cntr->ops->pre_process) {
err = cntr->ops->pre_process(cntr, format, samples_per_frame,
frames_per_second, &byte_count);
if (err < 0)
return err;
if (cntr->eof)
return 0;
}
assert(*format >= SND_PCM_FORMAT_S8);
assert(*format <= SND_PCM_FORMAT_LAST);
assert(*samples_per_frame > 0);
assert(*frames_per_second > 0);
assert(byte_count > 0);
cntr->bytes_per_sample = snd_pcm_format_physical_width(*format) / 8;
cntr->samples_per_frame = *samples_per_frame;
cntr->frames_per_second = *frames_per_second;
bytes_per_frame = cntr->bytes_per_sample * *samples_per_frame;
*frame_count = byte_count / bytes_per_frame;
cntr->max_size -= cntr->max_size / bytes_per_frame;
if (cntr->verbose > 0) {
fprintf(stderr, "Container: %s\n",
cntr_type_labels[cntr->type]);
fprintf(stderr, " format: %s\n",
cntr_format_labels[cntr->format]);
fprintf(stderr, " sample format: %s\n",
snd_pcm_format_name(*format));
fprintf(stderr, " bytes/sample: %u\n",
cntr->bytes_per_sample);
fprintf(stderr, " samples/frame: %u\n",
cntr->samples_per_frame);
fprintf(stderr, " frames/second: %u\n",
cntr->frames_per_second);
if (cntr->type == CONTAINER_TYPE_PARSER) {
fprintf(stderr, " frames: %lu\n",
*frame_count);
} else {
fprintf(stderr, " max frames: %lu\n",
*frame_count);
}
}
return 0;
}
int container_context_process_frames(struct container_context *cntr,
void *frame_buffer,
unsigned int *frame_count)
{
char *buf = frame_buffer;
unsigned int bytes_per_frame;
unsigned int byte_count;
int err;
assert(cntr);
assert(!cntr->eof);
assert(frame_buffer);
assert(frame_count);
bytes_per_frame = cntr->bytes_per_sample * cntr->samples_per_frame;
byte_count = *frame_count * bytes_per_frame;
// Each container has limitation for its volume for sample data.
if (cntr->handled_byte_count > cntr->max_size - byte_count)
byte_count = cntr->max_size - cntr->handled_byte_count;
// All of supported containers include interleaved PCM frames.
// TODO: process frames for truncate case.
err = cntr->process_bytes(cntr, buf, byte_count);
if (err < 0) {
*frame_count = 0;
return err;
}
cntr->handled_byte_count += byte_count;
if (cntr->handled_byte_count == cntr->max_size)
cntr->eof = true;
*frame_count = byte_count / bytes_per_frame;
return 0;
}
int container_context_post_process(struct container_context *cntr,
uint64_t *frame_count)
{
int err = 0;
assert(cntr);
assert(frame_count);
if (cntr->verbose && cntr->handled_byte_count > 0) {
fprintf(stderr, " Handled bytes: %lu\n",
cntr->handled_byte_count);
}
// NOTE* we cannot seek when using standard input/output.
if (!cntr->stdio && cntr->ops && cntr->ops->post_process) {
// Usually, need to write out processed bytes in container
// header even it this program is interrupted.
cntr->interrupted = false;
err = cntr->ops->post_process(cntr, cntr->handled_byte_count);
}
// Ensure to perform write-back from disk cache.
if (cntr->type == CONTAINER_TYPE_BUILDER)
fsync(cntr->fd);
if (err < 0)
return err;
if (cntr->bytes_per_sample == 0 || cntr->samples_per_frame == 0) {
*frame_count = 0;
} else {
*frame_count = cntr->handled_byte_count /
cntr->bytes_per_sample /
cntr->samples_per_frame;
}
return 0;
}
void container_context_destroy(struct container_context *cntr)
{
assert(cntr);
close(cntr->fd);
if (cntr->private_data)
free(cntr->private_data);
cntr->fd = 0;
cntr->private_data = NULL;
}