// SPDX-License-Identifier: GPL-2.0 // // container.c - an interface of parser/builder for formatted files. // // Copyright (c) 2018 Takashi Sakamoto // // Licensed under the terms of the GNU General Public License, version 2. #include "container.h" #include "misc.h" #include #include #include #include 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", [CONTAINER_FORMAT_RAW] = "raw", }; static const char *const suffixes[] = { [CONTAINER_FORMAT_RIFF_WAVE] = ".wav", [CONTAINER_FORMAT_AU] = ".au", [CONTAINER_FORMAT_VOC] = ".voc", [CONTAINER_FORMAT_RAW] = "", }; 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_RAW; } 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); if (isatty(cntr->fd)) { fprintf(stderr, "A terminal is referred for standard input. " "Output from any process or shell redirection " "should be referred instead.\n"); return -EIO; } 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)) parser = &container_parser_raw; // 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, [CONTAINER_FORMAT_RAW] = &container_builder_raw, }; 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); if (isatty(cntr->fd)) { fprintf(stderr, "A terminal is referred for standard output. " "Input to any process or shell redirection " "should be referred instead.\n"); return -EIO; } 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 = 0; 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; } if (cntr->format == CONTAINER_FORMAT_RAW) { if (*format == SND_PCM_FORMAT_UNKNOWN || *samples_per_frame == 0 || *frames_per_second == 0) { fprintf(stderr, "Any file format is not detected. Need to " "indicate all of sample format, channels and " "rate explicitly.\n"); return -EINVAL; } } 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; unsigned int target_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; target_byte_count = *frame_count * bytes_per_frame; // A parser of cotainers already read first 4 bytes to detect format // of container, however they includes PCM frames when any format was // undetected. Surely to write out them. byte_count = target_byte_count; if (cntr->format == CONTAINER_FORMAT_RAW && cntr->type == CONTAINER_TYPE_PARSER && !cntr->magic_handled) { memcpy(buf, cntr->magic, sizeof(cntr->magic)); buf += sizeof(cntr->magic); byte_count -= sizeof(cntr->magic); cntr->magic_handled = true; } // 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 += target_byte_count; if (cntr->handled_byte_count == cntr->max_size) cntr->eof = true; *frame_count = target_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; }