1// SPDX-License-Identifier: GPL-2.0 2// 3// container.c - an interface of parser/builder for formatted files. 4// 5// Copyright (c) 2018 Takashi Sakamoto <o-takashi@sakamocchi.jp> 6// 7// Licensed under the terms of the GNU General Public License, version 2. 8 9#include "container.h" 10#include "misc.h" 11 12#include <stdio.h> 13#include <errno.h> 14#include <string.h> 15#include <fcntl.h> 16#include <inttypes.h> 17 18static const char *const cntr_type_labels[] = { 19 [CONTAINER_TYPE_PARSER] = "parser", 20 [CONTAINER_TYPE_BUILDER] = "builder", 21}; 22 23static const char *const cntr_format_labels[] = { 24 [CONTAINER_FORMAT_RIFF_WAVE] = "riff/wave", 25 [CONTAINER_FORMAT_AU] = "au", 26 [CONTAINER_FORMAT_VOC] = "voc", 27 [CONTAINER_FORMAT_RAW] = "raw", 28}; 29 30static const char *const suffixes[] = { 31 [CONTAINER_FORMAT_RIFF_WAVE] = ".wav", 32 [CONTAINER_FORMAT_AU] = ".au", 33 [CONTAINER_FORMAT_VOC] = ".voc", 34 [CONTAINER_FORMAT_RAW] = "", 35}; 36 37const char * container_suffix_from_format(enum container_format format) 38{ 39 return suffixes[format]; 40} 41 42int container_recursive_read(struct container_context *cntr, void *buf, 43 unsigned int byte_count) 44{ 45 char *dst = buf; 46 ssize_t result; 47 size_t consumed = 0; 48 49 while (consumed < byte_count && !cntr->interrupted) { 50 result = read(cntr->fd, dst + consumed, byte_count - consumed); 51 if (result < 0) { 52 // This descriptor was configured with non-blocking 53 // mode. EINTR is not cought when get any interrupts. 54 if (cntr->interrupted) 55 return -EINTR; 56 if (errno == EAGAIN) 57 continue; 58 return -errno; 59 } 60 // Reach EOF. 61 if (result == 0) { 62 cntr->eof = true; 63 return 0; 64 } 65 66 consumed += result; 67 } 68 69 return 0; 70} 71 72int container_recursive_write(struct container_context *cntr, void *buf, 73 unsigned int byte_count) 74{ 75 char *src = buf; 76 ssize_t result; 77 size_t consumed = 0; 78 79 while (consumed < byte_count && !cntr->interrupted) { 80 result = write(cntr->fd, src + consumed, byte_count - consumed); 81 if (result < 0) { 82 // This descriptor was configured with non-blocking 83 // mode. EINTR is not cought when get any interrupts. 84 if (cntr->interrupted) 85 return -EINTR; 86 if (errno == EAGAIN) 87 continue; 88 return -errno; 89 } 90 91 consumed += result; 92 } 93 94 return 0; 95} 96 97enum container_format container_format_from_path(const char *path) 98{ 99 const char *suffix; 100 const char *pos; 101 int i; 102 103 for (i = 0; i < (int)ARRAY_SIZE(suffixes); ++i) { 104 suffix = suffixes[i]; 105 106 // Check last part of the string. 107 pos = path + strlen(path) - strlen(suffix); 108 if (!strcmp(pos, suffix)) 109 return i; 110 } 111 112 // Unsupported. 113 return CONTAINER_FORMAT_RAW; 114} 115 116int container_seek_offset(struct container_context *cntr, off_t offset) 117{ 118 off_t pos; 119 120 pos = lseek(cntr->fd, offset, SEEK_SET); 121 if (pos < 0) 122 return -errno; 123 if (pos != offset) 124 return -EIO; 125 126 return 0; 127} 128 129// To avoid blocking execution at system call iteration after receiving UNIX 130// signals. 131static int set_nonblock_flag(int fd) 132{ 133 int flags; 134 135 flags = fcntl(fd, F_GETFL); 136 if (flags < 0) 137 return -errno; 138 139 flags |= O_NONBLOCK; 140 if (fcntl(fd, F_SETFL, flags) < 0) 141 return -errno; 142 143 return 0; 144} 145 146int container_parser_init(struct container_context *cntr, int fd, 147 unsigned int verbose) 148{ 149 const struct container_parser *parsers[] = { 150 [CONTAINER_FORMAT_RIFF_WAVE] = &container_parser_riff_wave, 151 [CONTAINER_FORMAT_AU] = &container_parser_au, 152 [CONTAINER_FORMAT_VOC] = &container_parser_voc, 153 }; 154 const struct container_parser *parser; 155 unsigned int size; 156 int i; 157 int err; 158 159 assert(cntr); 160 assert(fd >= 0); 161 162 // Detect forgotten to destruct. 163 assert(cntr->fd == 0); 164 assert(cntr->private_data == NULL); 165 166 memset(cntr, 0, sizeof(*cntr)); 167 168 cntr->fd = fd; 169 170 cntr->stdio = (cntr->fd == fileno(stdin)); 171 if (cntr->stdio) { 172 if (isatty(cntr->fd)) { 173 fprintf(stderr, 174 "A terminal is referred for standard input. " 175 "Output from any process or shell redirection " 176 "should be referred instead.\n"); 177 return -EIO; 178 } 179 } 180 181 err = set_nonblock_flag(cntr->fd); 182 if (err < 0) 183 return err; 184 185 // 4 bytes are enough to detect supported containers. 186 err = container_recursive_read(cntr, cntr->magic, sizeof(cntr->magic)); 187 if (err < 0) 188 return err; 189 for (i = 0; i < (int)ARRAY_SIZE(parsers); ++i) { 190 parser = parsers[i]; 191 size = strlen(parser->magic); 192 if (size > 4) 193 size = 4; 194 if (!strncmp(cntr->magic, parser->magic, size)) 195 break; 196 } 197 198 // Don't forget that the first 4 bytes were already read for magic 199 // bytes. 200 cntr->magic_handled = false; 201 202 // Unless detected, use raw container. 203 if (i == ARRAY_SIZE(parsers)) 204 parser = &container_parser_raw; 205 206 // Allocate private data for the parser. 207 if (parser->private_size > 0) { 208 cntr->private_data = malloc(parser->private_size); 209 if (cntr->private_data == NULL) 210 return -ENOMEM; 211 memset(cntr->private_data, 0, parser->private_size); 212 } 213 214 cntr->type = CONTAINER_TYPE_PARSER; 215 cntr->process_bytes = container_recursive_read; 216 cntr->format = parser->format; 217 cntr->ops = &parser->ops; 218 cntr->max_size = parser->max_size; 219 cntr->verbose = verbose; 220 221 return 0; 222} 223 224int container_builder_init(struct container_context *cntr, int fd, 225 enum container_format format, unsigned int verbose) 226{ 227 const struct container_builder *builders[] = { 228 [CONTAINER_FORMAT_RIFF_WAVE] = &container_builder_riff_wave, 229 [CONTAINER_FORMAT_AU] = &container_builder_au, 230 [CONTAINER_FORMAT_VOC] = &container_builder_voc, 231 [CONTAINER_FORMAT_RAW] = &container_builder_raw, 232 }; 233 const struct container_builder *builder; 234 int err; 235 236 assert(cntr); 237 assert(fd >= 0); 238 239 // Detect forgotten to destruct. 240 assert(cntr->fd == 0); 241 assert(cntr->private_data == NULL); 242 243 memset(cntr, 0, sizeof(*cntr)); 244 245 cntr->fd = fd; 246 247 cntr->stdio = (cntr->fd == fileno(stdout)); 248 if (cntr->stdio) { 249 if (isatty(cntr->fd)) { 250 fprintf(stderr, 251 "A terminal is referred for standard output. " 252 "Input to any process or shell redirection " 253 "should be referred instead.\n"); 254 return -EIO; 255 } 256 } 257 258 err = set_nonblock_flag(cntr->fd); 259 if (err < 0) 260 return err; 261 262 builder = builders[format]; 263 264 // Allocate private data for the builder. 265 if (builder->private_size > 0) { 266 cntr->private_data = malloc(builder->private_size); 267 if (cntr->private_data == NULL) 268 return -ENOMEM; 269 memset(cntr->private_data, 0, builder->private_size); 270 } 271 272 cntr->type = CONTAINER_TYPE_BUILDER; 273 cntr->process_bytes = container_recursive_write; 274 cntr->format = builder->format; 275 cntr->ops = &builder->ops; 276 cntr->max_size = builder->max_size; 277 cntr->verbose = verbose; 278 279 return 0; 280} 281 282int container_context_pre_process(struct container_context *cntr, 283 snd_pcm_format_t *format, 284 unsigned int *samples_per_frame, 285 unsigned int *frames_per_second, 286 uint64_t *frame_count) 287{ 288 uint64_t byte_count = 0; 289 unsigned int bytes_per_frame; 290 int err; 291 292 assert(cntr); 293 assert(format); 294 assert(samples_per_frame); 295 assert(frames_per_second); 296 assert(frame_count); 297 298 if (cntr->type == CONTAINER_TYPE_BUILDER) 299 byte_count = cntr->max_size; 300 301 if (cntr->ops->pre_process) { 302 err = cntr->ops->pre_process(cntr, format, samples_per_frame, 303 frames_per_second, &byte_count); 304 if (err < 0) 305 return err; 306 if (cntr->eof) 307 return 0; 308 } 309 310 if (cntr->format == CONTAINER_FORMAT_RAW) { 311 if (*format == SND_PCM_FORMAT_UNKNOWN || 312 *samples_per_frame == 0 || *frames_per_second == 0) { 313 fprintf(stderr, 314 "Any file format is not detected. Need to " 315 "indicate all of sample format, channels and " 316 "rate explicitly.\n"); 317 return -EINVAL; 318 } 319 } 320 assert(*format >= SND_PCM_FORMAT_S8); 321 assert(*format <= SND_PCM_FORMAT_LAST); 322 assert(*samples_per_frame > 0); 323 assert(*frames_per_second > 0); 324 assert(byte_count > 0); 325 326 cntr->bytes_per_sample = snd_pcm_format_physical_width(*format) / 8; 327 cntr->samples_per_frame = *samples_per_frame; 328 cntr->frames_per_second = *frames_per_second; 329 330 bytes_per_frame = cntr->bytes_per_sample * *samples_per_frame; 331 *frame_count = byte_count / bytes_per_frame; 332 cntr->max_size -= cntr->max_size / bytes_per_frame; 333 334 if (cntr->verbose > 0) { 335 fprintf(stderr, "Container: %s\n", 336 cntr_type_labels[cntr->type]); 337 fprintf(stderr, " format: %s\n", 338 cntr_format_labels[cntr->format]); 339 fprintf(stderr, " sample format: %s\n", 340 snd_pcm_format_name(*format)); 341 fprintf(stderr, " bytes/sample: %u\n", 342 cntr->bytes_per_sample); 343 fprintf(stderr, " samples/frame: %u\n", 344 cntr->samples_per_frame); 345 fprintf(stderr, " frames/second: %u\n", 346 cntr->frames_per_second); 347 if (cntr->type == CONTAINER_TYPE_PARSER) { 348 fprintf(stderr, " frames: %" PRIu64 "\n", 349 *frame_count); 350 } else { 351 fprintf(stderr, " max frames: %" PRIu64 "\n", 352 *frame_count); 353 } 354 } 355 356 return 0; 357} 358 359int container_context_process_frames(struct container_context *cntr, 360 void *frame_buffer, 361 unsigned int *frame_count) 362{ 363 char *buf = frame_buffer; 364 unsigned int bytes_per_frame; 365 unsigned int byte_count; 366 unsigned int target_byte_count; 367 int err; 368 369 assert(cntr); 370 assert(!cntr->eof); 371 assert(frame_buffer); 372 assert(frame_count); 373 374 bytes_per_frame = cntr->bytes_per_sample * cntr->samples_per_frame; 375 target_byte_count = *frame_count * bytes_per_frame; 376 377 // A parser of cotainers already read first 4 bytes to detect format 378 // of container, however they includes PCM frames when any format was 379 // undetected. Surely to write out them. 380 byte_count = target_byte_count; 381 if (cntr->format == CONTAINER_FORMAT_RAW && 382 cntr->type == CONTAINER_TYPE_PARSER && !cntr->magic_handled) { 383 memcpy(buf, cntr->magic, sizeof(cntr->magic)); 384 buf += sizeof(cntr->magic); 385 byte_count -= sizeof(cntr->magic); 386 cntr->magic_handled = true; 387 } 388 389 // Each container has limitation for its volume for sample data. 390 if (cntr->handled_byte_count > cntr->max_size - byte_count) 391 byte_count = cntr->max_size - cntr->handled_byte_count; 392 393 // All of supported containers include interleaved PCM frames. 394 // TODO: process frames for truncate case. 395 err = cntr->process_bytes(cntr, buf, byte_count); 396 if (err < 0) { 397 *frame_count = 0; 398 return err; 399 } 400 401 cntr->handled_byte_count += target_byte_count; 402 if (cntr->handled_byte_count == cntr->max_size) 403 cntr->eof = true; 404 405 *frame_count = target_byte_count / bytes_per_frame; 406 407 return 0; 408} 409 410int container_context_post_process(struct container_context *cntr, 411 uint64_t *frame_count) 412{ 413 int err = 0; 414 415 assert(cntr); 416 assert(frame_count); 417 418 if (cntr->verbose && cntr->handled_byte_count > 0) { 419 fprintf(stderr, " Handled bytes: %" PRIu64 "\n", 420 cntr->handled_byte_count); 421 } 422 423 // NOTE* we cannot seek when using standard input/output. 424 if (!cntr->stdio && cntr->ops && cntr->ops->post_process) { 425 // Usually, need to write out processed bytes in container 426 // header even it this program is interrupted. 427 cntr->interrupted = false; 428 429 err = cntr->ops->post_process(cntr, cntr->handled_byte_count); 430 } 431 432 // Ensure to perform write-back from disk cache. 433 if (cntr->type == CONTAINER_TYPE_BUILDER) 434 fsync(cntr->fd); 435 436 if (err < 0) 437 return err; 438 439 if (cntr->bytes_per_sample == 0 || cntr->samples_per_frame == 0) { 440 *frame_count = 0; 441 } else { 442 *frame_count = cntr->handled_byte_count / 443 cntr->bytes_per_sample / 444 cntr->samples_per_frame; 445 } 446 447 return 0; 448} 449 450void container_context_destroy(struct container_context *cntr) 451{ 452 assert(cntr); 453 454 if (cntr->private_data) 455 free(cntr->private_data); 456 457 cntr->fd = 0; 458 cntr->private_data = NULL; 459} 460