xref: /third_party/alsa-utils/axfer/container.c (revision c72fcc34)
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