1 // SPDX-License-Identifier: GPL-2.0
3 // container.c - an interface of parser/builder for formatted files.
5 // Copyright (c) 2018 Takashi Sakamoto <o-takashi@sakamocchi.jp>
7 // Licensed under the terms of the GNU General Public License, version 2.
17 static const char *const cntr_type_labels[] = {
18 [CONTAINER_TYPE_PARSER] = "parser",
19 [CONTAINER_TYPE_BUILDER] = "builder",
22 static const char *const cntr_format_labels[] = {
23 [CONTAINER_FORMAT_RIFF_WAVE] = "riff/wave",
24 [CONTAINER_FORMAT_AU] = "au",
25 [CONTAINER_FORMAT_VOC] = "voc",
26 [CONTAINER_FORMAT_RAW] = "raw",
29 static const char *const suffixes[] = {
30 [CONTAINER_FORMAT_RIFF_WAVE] = ".wav",
31 [CONTAINER_FORMAT_AU] = ".au",
32 [CONTAINER_FORMAT_VOC] = ".voc",
33 [CONTAINER_FORMAT_RAW] = "",
36 const char *const container_suffix_from_format(enum container_format format)
38 return suffixes[format];
41 int container_recursive_read(struct container_context *cntr, void *buf,
42 unsigned int byte_count)
48 while (consumed < byte_count && !cntr->interrupted) {
49 result = read(cntr->fd, dst + consumed, byte_count - consumed);
51 // This descriptor was configured with non-blocking
52 // mode. EINTR is not cought when get any interrupts.
53 if (cntr->interrupted)
71 int container_recursive_write(struct container_context *cntr, void *buf,
72 unsigned int byte_count)
78 while (consumed < byte_count && !cntr->interrupted) {
79 result = write(cntr->fd, src + consumed, byte_count - consumed);
81 // This descriptor was configured with non-blocking
82 // mode. EINTR is not cought when get any interrupts.
83 if (cntr->interrupted)
96 enum container_format container_format_from_path(const char *path)
102 for (i = 0; i < ARRAY_SIZE(suffixes); ++i) {
103 suffix = suffixes[i];
105 // Check last part of the string.
106 pos = path + strlen(path) - strlen(suffix);
107 if (!strcmp(pos, suffix))
112 return CONTAINER_FORMAT_RAW;
115 int container_seek_offset(struct container_context *cntr, off64_t offset)
119 pos = lseek64(cntr->fd, offset, SEEK_SET);
128 // To avoid blocking execution at system call iteration after receiving UNIX
130 static int set_nonblock_flag(int fd)
134 flags = fcntl(fd, F_GETFL);
139 if (fcntl(fd, F_SETFL, flags) < 0)
145 int container_parser_init(struct container_context *cntr,
146 const char *const path, unsigned int verbose)
148 const struct container_parser *parsers[] = {
149 [CONTAINER_FORMAT_RIFF_WAVE] = &container_parser_riff_wave,
150 [CONTAINER_FORMAT_AU] = &container_parser_au,
151 [CONTAINER_FORMAT_VOC] = &container_parser_voc,
153 const struct container_parser *parser;
160 assert(path[0] != '\0');
162 // Detect forgotten to destruct.
163 assert(cntr->fd == 0);
164 assert(cntr->private_data == NULL);
166 memset(cntr, 0, sizeof(*cntr));
168 // Open a target descriptor.
169 if (!strcmp(path, "-")) {
170 cntr->fd = fileno(stdin);
171 if (isatty(cntr->fd)) {
173 "A terminal is referred for standard input. "
174 "Output from any process or shell redirection "
175 "should be referred instead.\n");
178 err = set_nonblock_flag(cntr->fd);
183 cntr->fd = open(path, O_RDONLY | O_NONBLOCK);
188 // 4 bytes are enough to detect supported containers.
189 err = container_recursive_read(cntr, cntr->magic, sizeof(cntr->magic));
192 for (i = 0; i < ARRAY_SIZE(parsers); ++i) {
194 size = strlen(parser->magic);
197 if (!strncmp(cntr->magic, parser->magic, size))
201 // Don't forget that the first 4 bytes were already read for magic
203 cntr->magic_handled = false;
205 // Unless detected, use raw container.
206 if (i == ARRAY_SIZE(parsers))
207 parser = &container_parser_raw;
209 // Allocate private data for the parser.
210 if (parser->private_size > 0) {
211 cntr->private_data = malloc(parser->private_size);
212 if (cntr->private_data == NULL)
214 memset(cntr->private_data, 0, parser->private_size);
217 cntr->type = CONTAINER_TYPE_PARSER;
218 cntr->process_bytes = container_recursive_read;
219 cntr->format = parser->format;
220 cntr->ops = &parser->ops;
221 cntr->max_size = parser->max_size;
222 cntr->verbose = verbose;
227 int container_builder_init(struct container_context *cntr,
228 const char *const path, enum container_format format,
229 unsigned int verbose)
231 const struct container_builder *builders[] = {
232 [CONTAINER_FORMAT_RIFF_WAVE] = &container_builder_riff_wave,
233 [CONTAINER_FORMAT_AU] = &container_builder_au,
234 [CONTAINER_FORMAT_VOC] = &container_builder_voc,
235 [CONTAINER_FORMAT_RAW] = &container_builder_raw,
237 const struct container_builder *builder;
242 assert(path[0] != '\0');
244 // Detect forgotten to destruct.
245 assert(cntr->fd == 0);
246 assert(cntr->private_data == NULL);
248 memset(cntr, 0, sizeof(*cntr));
250 // Open a target descriptor.
251 if (path == NULL || *path == '\0')
253 if (!strcmp(path, "-")) {
254 cntr->fd = fileno(stdout);
255 if (isatty(cntr->fd)) {
257 "A terminal is referred for standard output. "
258 "Input to any process or shell redirection "
259 "should be referred instead.\n");
262 err = set_nonblock_flag(cntr->fd);
267 cntr->fd = open(path, O_RDWR | O_NONBLOCK | O_CREAT | O_TRUNC,
273 builder = builders[format];
275 // Allocate private data for the builder.
276 if (builder->private_size > 0) {
277 cntr->private_data = malloc(builder->private_size);
278 if (cntr->private_data == NULL)
280 memset(cntr->private_data, 0, builder->private_size);
283 cntr->type = CONTAINER_TYPE_BUILDER;
284 cntr->process_bytes = container_recursive_write;
285 cntr->format = builder->format;
286 cntr->ops = &builder->ops;
287 cntr->max_size = builder->max_size;
288 cntr->verbose = verbose;
293 int container_context_pre_process(struct container_context *cntr,
294 snd_pcm_format_t *format,
295 unsigned int *samples_per_frame,
296 unsigned int *frames_per_second,
297 uint64_t *frame_count)
300 unsigned int bytes_per_frame;
305 assert(samples_per_frame);
306 assert(frames_per_second);
309 if (cntr->type == CONTAINER_TYPE_BUILDER)
310 byte_count = cntr->max_size;
312 if (cntr->ops->pre_process) {
313 err = cntr->ops->pre_process(cntr, format, samples_per_frame,
314 frames_per_second, &byte_count);
321 if (cntr->format == CONTAINER_FORMAT_RAW) {
322 if (*format == SND_PCM_FORMAT_UNKNOWN ||
323 *samples_per_frame == 0 || *frames_per_second == 0) {
325 "Any file format is not detected. Need to "
326 "indicate all of sample format, channels and "
327 "rate explicitly.\n");
331 assert(*format >= SND_PCM_FORMAT_S8);
332 assert(*format <= SND_PCM_FORMAT_LAST);
333 assert(*samples_per_frame > 0);
334 assert(*frames_per_second > 0);
335 assert(byte_count > 0);
337 cntr->bytes_per_sample = snd_pcm_format_physical_width(*format) / 8;
338 cntr->samples_per_frame = *samples_per_frame;
339 cntr->frames_per_second = *frames_per_second;
341 bytes_per_frame = cntr->bytes_per_sample * *samples_per_frame;
342 *frame_count = byte_count / bytes_per_frame;
343 cntr->max_size -= cntr->max_size / bytes_per_frame;
345 if (cntr->verbose > 0) {
346 fprintf(stderr, "Container: %s\n",
347 cntr_type_labels[cntr->type]);
348 fprintf(stderr, " format: %s\n",
349 cntr_format_labels[cntr->format]);
350 fprintf(stderr, " sample format: %s\n",
351 snd_pcm_format_name(*format));
352 fprintf(stderr, " bytes/sample: %u\n",
353 cntr->bytes_per_sample);
354 fprintf(stderr, " samples/frame: %u\n",
355 cntr->samples_per_frame);
356 fprintf(stderr, " frames/second: %u\n",
357 cntr->frames_per_second);
358 if (cntr->type == CONTAINER_TYPE_PARSER) {
359 fprintf(stderr, " frames: %lu\n",
362 fprintf(stderr, " max frames: %lu\n",
370 int container_context_process_frames(struct container_context *cntr,
372 unsigned int *frame_count)
374 char *buf = frame_buffer;
375 unsigned int bytes_per_frame;
376 unsigned int byte_count;
377 unsigned int target_byte_count;
382 assert(frame_buffer);
385 bytes_per_frame = cntr->bytes_per_sample * cntr->samples_per_frame;
386 target_byte_count = *frame_count * bytes_per_frame;
388 // A parser of cotainers already read first 4 bytes to detect format
389 // of container, however they includes PCM frames when any format was
390 // undetected. Surely to write out them.
391 byte_count = target_byte_count;
392 if (cntr->format == CONTAINER_FORMAT_RAW &&
393 cntr->type == CONTAINER_TYPE_PARSER && !cntr->magic_handled) {
394 memcpy(buf, cntr->magic, sizeof(cntr->magic));
395 buf += sizeof(cntr->magic);
396 byte_count -= sizeof(cntr->magic);
397 cntr->magic_handled = true;
400 // Each container has limitation for its volume for sample data.
401 if (cntr->handled_byte_count > cntr->max_size - byte_count)
402 byte_count = cntr->max_size - cntr->handled_byte_count;
404 // All of supported containers include interleaved PCM frames.
405 // TODO: process frames for truncate case.
406 err = cntr->process_bytes(cntr, buf, byte_count);
412 cntr->handled_byte_count += target_byte_count;
413 if (cntr->handled_byte_count == cntr->max_size)
416 *frame_count = target_byte_count / bytes_per_frame;
421 int container_context_post_process(struct container_context *cntr,
422 uint64_t *frame_count)
429 if (cntr->verbose && cntr->handled_byte_count > 0) {
430 fprintf(stderr, " Handled bytes: %lu\n",
431 cntr->handled_byte_count);
434 // NOTE* we cannot seek when using standard input/output.
435 if (!cntr->stdio && cntr->ops && cntr->ops->post_process) {
436 // Usually, need to write out processed bytes in container
437 // header even it this program is interrupted.
438 cntr->interrupted = false;
440 err = cntr->ops->post_process(cntr, cntr->handled_byte_count);
443 // Ensure to perform write-back from disk cache.
444 if (cntr->type == CONTAINER_TYPE_BUILDER)
450 if (cntr->bytes_per_sample == 0 || cntr->samples_per_frame == 0) {
453 *frame_count = cntr->handled_byte_count /
454 cntr->bytes_per_sample /
455 cntr->samples_per_frame;
461 void container_context_destroy(struct container_context *cntr)
466 if (cntr->private_data)
467 free(cntr->private_data);
470 cntr->private_data = NULL;