1// SPDX-License-Identifier: GPL-2.0
2//
3// subcmd-transfer.c - operations for transfer sub command.
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 "xfer.h"
10#include "subcmd.h"
11#include "misc.h"
12
13#include <signal.h>
14#include <inttypes.h>
15
16struct context {
17	struct xfer_context xfer;
18	struct mapper_context mapper;
19	struct container_context *cntrs;
20	unsigned int cntr_count;
21
22	int *cntr_fds;
23
24	// NOTE: To handling Unix signal.
25	bool interrupted;
26	int signal;
27};
28
29// NOTE: To handling Unix signal.
30static struct context *ctx_ptr;
31
32static void handle_unix_signal_for_finish(int sig)
33{
34	unsigned int i;
35
36	for (i = 0; i < ctx_ptr->cntr_count; ++i)
37		ctx_ptr->cntrs[i].interrupted = true;
38
39	ctx_ptr->signal = sig;
40	ctx_ptr->interrupted = true;
41}
42
43static void handle_unix_signal_for_suspend(int sig ATTRIBUTE_UNUSED)
44{
45	sigset_t curr, prev;
46	struct sigaction sa = {0};
47
48	// 1. suspend substream.
49	xfer_context_pause(&ctx_ptr->xfer, true);
50
51	// 2. Prepare for default handler(SIG_DFL) of SIGTSTP to stop this
52	// process.
53	if (sigaction(SIGTSTP, NULL, &sa) < 0) {
54		fprintf(stderr, "sigaction(2)\n");
55		exit(EXIT_FAILURE);
56	}
57	if (sa.sa_handler == SIG_ERR)
58		exit(EXIT_FAILURE);
59	if (sa.sa_handler == handle_unix_signal_for_suspend)
60		sa.sa_handler = SIG_DFL;
61	if (sigaction(SIGTSTP, &sa, NULL) < 0) {
62		fprintf(stderr, "sigaction(2)\n");
63		exit(EXIT_FAILURE);
64	}
65
66	// Queue SIGTSTP.
67	raise(SIGTSTP);
68
69	// Release the queued signal from being blocked. This causes an
70	// additional interrupt for the default handler.
71	sigemptyset(&curr);
72	sigaddset(&curr, SIGTSTP);
73	if (sigprocmask(SIG_UNBLOCK, &curr, &prev) < 0) {
74		fprintf(stderr, "sigprocmask(2)\n");
75		exit(EXIT_FAILURE);
76	}
77
78	// 3. SIGCONT is cought and rescheduled. Recover blocking status of
79	// UNIX signals.
80	if (sigprocmask(SIG_SETMASK, &prev, NULL) < 0) {
81		fprintf(stderr, "sigprocmask(2)\n");
82		exit(EXIT_FAILURE);
83	}
84
85	// Reconfigure this handler for SIGTSTP, instead of default one.
86	sigemptyset(&sa.sa_mask);
87	sa.sa_flags = SA_RESTART;
88	sa.sa_handler = handle_unix_signal_for_suspend;
89	if (sigaction(SIGTSTP, &sa, NULL) < 0) {
90		fprintf(stderr, "sigaction(2)\n");
91		exit(EXIT_FAILURE);
92	}
93
94	// 4. Continue the PCM substream.
95	xfer_context_pause(&ctx_ptr->xfer, false);
96}
97
98static int prepare_signal_handler(struct context *ctx)
99{
100	struct sigaction sa = {0};
101
102	sigemptyset(&sa.sa_mask);
103	sa.sa_flags = 0;
104	sa.sa_handler = handle_unix_signal_for_finish;
105
106	if (sigaction(SIGINT, &sa, NULL) < 0)
107		return -errno;
108	if (sigaction(SIGTERM, &sa, NULL) < 0)
109		return -errno;
110
111	sigemptyset(&sa.sa_mask);
112	sa.sa_flags = 0;
113	sa.sa_handler = handle_unix_signal_for_suspend;
114	if (sigaction(SIGTSTP, &sa, NULL) < 0)
115		return -errno;
116
117	ctx_ptr = ctx;
118
119	return 0;
120}
121
122static int context_init(struct context *ctx, snd_pcm_stream_t direction,
123			int argc, char *const *argv)
124{
125	const char *xfer_type_literal;
126	enum xfer_type xfer_type;
127	int i;
128
129	// Decide transfer backend before option parser runs.
130	xfer_type_literal = NULL;
131	for (i = 0; i < argc; ++i) {
132		if (strstr(argv[i], "--xfer-type") != argv[i])
133			continue;
134		xfer_type_literal = argv[i] + 12;
135	}
136	if (xfer_type_literal == NULL) {
137		xfer_type = XFER_TYPE_LIBASOUND;
138	} else {
139		xfer_type = xfer_type_from_label(xfer_type_literal);
140		if (xfer_type == XFER_TYPE_UNSUPPORTED) {
141			fprintf(stderr, "The '%s' xfer type is not supported\n",
142				xfer_type_literal);
143			return -EINVAL;
144		}
145	}
146
147	// Initialize transfer.
148	return xfer_context_init(&ctx->xfer, xfer_type, direction, argc, argv);
149}
150
151static int allocate_containers(struct context *ctx, unsigned int count)
152{
153	ctx->cntrs = calloc(count, sizeof(*ctx->cntrs));
154	if (ctx->cntrs == NULL)
155		return -ENOMEM;
156	ctx->cntr_count = count;
157
158	ctx->cntr_fds = calloc(count, sizeof(*ctx->cntr_fds));
159	if (ctx->cntr_fds == NULL)
160		return -ENOMEM;
161
162	return 0;
163}
164
165static int capture_pre_process(struct context *ctx, snd_pcm_access_t *access,
166			       snd_pcm_uframes_t *frames_per_buffer,
167			       uint64_t *total_frame_count)
168{
169	snd_pcm_format_t sample_format = SND_PCM_FORMAT_UNKNOWN;
170	unsigned int samples_per_frame = 0;
171	unsigned int frames_per_second = 0;
172	unsigned int channels;
173	unsigned int i;
174	int err;
175
176	err = xfer_context_pre_process(&ctx->xfer, &sample_format,
177				       &samples_per_frame, &frames_per_second,
178				       access, frames_per_buffer);
179	if (err < 0)
180		return err;
181
182	// Prepare for containers.
183	err = allocate_containers(ctx, ctx->xfer.path_count);
184	if (err < 0)
185		return err;
186
187	if (ctx->cntr_count > 1)
188		channels = 1;
189	else
190		channels = samples_per_frame;
191
192	*total_frame_count = 0;
193	for (i = 0; i < ctx->cntr_count; ++i) {
194		const char *path = ctx->xfer.paths[i];
195		int fd;
196		uint64_t frame_count;
197
198		if (!strcmp(path, "-")) {
199			fd = fileno(stdout);
200		} else {
201			fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0644);
202			if (fd < 0)
203				return -errno;
204		}
205		ctx->cntr_fds[i] = fd;
206
207		err = container_builder_init(ctx->cntrs + i, ctx->cntr_fds[i],
208					     ctx->xfer.cntr_format,
209					     ctx->xfer.verbose > 1);
210		if (err < 0)
211			return err;
212
213		err = container_context_pre_process(ctx->cntrs + i,
214						    &sample_format, &channels,
215						    &frames_per_second,
216						    &frame_count);
217		if (err < 0)
218			return err;
219
220		if (*total_frame_count == 0)
221			*total_frame_count = frame_count;
222		if (frame_count < *total_frame_count)
223			*total_frame_count = frame_count;
224	}
225
226	return 0;
227}
228
229static int playback_pre_process(struct context *ctx, snd_pcm_access_t *access,
230				snd_pcm_uframes_t *frames_per_buffer,
231				uint64_t *total_frame_count)
232{
233	snd_pcm_format_t sample_format = SND_PCM_FORMAT_UNKNOWN;
234	unsigned int samples_per_frame = 0;
235	unsigned int frames_per_second = 0;
236	unsigned int i;
237	int err;
238
239	// Prepare for containers.
240	err = allocate_containers(ctx, ctx->xfer.path_count);
241	if (err < 0)
242		return err;
243
244	for (i = 0; i < ctx->cntr_count; ++i) {
245		const char *path = ctx->xfer.paths[i];
246		int fd;
247		snd_pcm_format_t format;
248		unsigned int channels;
249		unsigned int rate;
250		uint64_t frame_count;
251
252		if (!strcmp(path, "-")) {
253			fd = fileno(stdin);
254		} else {
255			fd = open(path, O_RDONLY);
256			if (fd < 0)
257				return -errno;
258		}
259		ctx->cntr_fds[i] = fd;
260
261		err = container_parser_init(ctx->cntrs + i, ctx->cntr_fds[i],
262					    ctx->xfer.verbose > 1);
263		if (err < 0)
264			return err;
265
266		if (i == 0) {
267			// For a raw container.
268			format = ctx->xfer.sample_format;
269			channels = ctx->xfer.samples_per_frame;
270			rate = ctx->xfer.frames_per_second;
271		} else {
272			format = sample_format;
273			channels = samples_per_frame;
274			rate = frames_per_second;
275		}
276
277		err = container_context_pre_process(ctx->cntrs + i, &format,
278						    &channels, &rate,
279						    &frame_count);
280		if (err < 0)
281			return err;
282
283		if (format == SND_PCM_FORMAT_UNKNOWN || channels == 0 ||
284		    rate == 0) {
285			fprintf(stderr,
286				"Sample format, channels and rate should be "
287				"indicated for given files.\n");
288			return -EINVAL;
289		}
290
291		if (i == 0) {
292			sample_format = format;
293			samples_per_frame = channels;
294			frames_per_second = rate;
295			*total_frame_count = frame_count;
296		} else {
297			if (format != sample_format) {
298				fprintf(stderr,
299					"When using several files, they "
300					"should include the same sample "
301					"format.\n");
302				return -EINVAL;
303			}
304
305			// No need to check channels to handle multiple
306			// containers.
307			if (rate != frames_per_second) {
308				fprintf(stderr,
309					"When using several files, they "
310					"should include samples at the same "
311					"sampling rate.\n");
312				return -EINVAL;
313			}
314			if (frame_count < *total_frame_count)
315				*total_frame_count = frame_count;
316		}
317	}
318
319	if (ctx->cntr_count > 1)
320		samples_per_frame = ctx->cntr_count;
321
322	// Configure hardware with these parameters.
323	return xfer_context_pre_process(&ctx->xfer, &sample_format,
324					&samples_per_frame, &frames_per_second,
325					access, frames_per_buffer);
326}
327
328static int context_pre_process(struct context *ctx, snd_pcm_stream_t direction,
329			       uint64_t *total_frame_count)
330{
331	snd_pcm_access_t access;
332	snd_pcm_uframes_t frames_per_buffer = 0;
333	unsigned int bytes_per_sample = 0;
334	enum mapper_type mapper_type;
335	int err;
336
337	if (direction == SND_PCM_STREAM_CAPTURE) {
338		mapper_type = MAPPER_TYPE_DEMUXER;
339		err = capture_pre_process(ctx, &access, &frames_per_buffer,
340					  total_frame_count);
341	} else {
342		mapper_type = MAPPER_TYPE_MUXER;
343		err = playback_pre_process(ctx, &access, &frames_per_buffer,
344					   total_frame_count);
345	}
346	if (err < 0)
347		return err;
348
349	// Prepare for mapper.
350	err = mapper_context_init(&ctx->mapper, mapper_type, ctx->cntr_count,
351				  ctx->xfer.verbose > 1);
352	if (err < 0)
353		return err;
354
355	bytes_per_sample =
356		snd_pcm_format_physical_width(ctx->xfer.sample_format) / 8;
357	if (bytes_per_sample <= 0)
358		return -ENXIO;
359	err = mapper_context_pre_process(&ctx->mapper, access, bytes_per_sample,
360					 ctx->xfer.samples_per_frame,
361					 frames_per_buffer, ctx->cntrs);
362	if (err < 0)
363		return err;
364
365	xfer_options_calculate_duration(&ctx->xfer, total_frame_count);
366
367	return 0;
368}
369
370static int context_process_frames(struct context *ctx,
371				  snd_pcm_stream_t direction,
372				  uint64_t expected_frame_count,
373				  uint64_t *actual_frame_count)
374{
375	bool verbose = ctx->xfer.verbose > 2;
376	unsigned int frame_count;
377	unsigned int i;
378	int err = 0;
379
380	if (!ctx->xfer.quiet) {
381		fprintf(stderr,
382			"%s: Format '%s', Rate %u Hz, Channels ",
383			snd_pcm_stream_name(direction),
384			snd_pcm_format_description(ctx->xfer.sample_format),
385			ctx->xfer.frames_per_second);
386		if (ctx->xfer.samples_per_frame == 1)
387			fprintf(stderr, "'monaural'");
388		else if (ctx->xfer.samples_per_frame == 2)
389			fprintf(stderr, "'Stereo'");
390		else
391			fprintf(stderr, "%u", ctx->xfer.samples_per_frame);
392		fprintf(stderr, "\n");
393	}
394
395	*actual_frame_count = 0;
396	while (!ctx->interrupted) {
397		struct container_context *cntr;
398
399		// Tell remains to expected frame count.
400		frame_count = expected_frame_count - *actual_frame_count;
401		err = xfer_context_process_frames(&ctx->xfer, &ctx->mapper,
402						  ctx->cntrs, &frame_count);
403		if (err < 0) {
404			if (err == -EAGAIN || err == -EINTR)
405				continue;
406			break;
407		}
408		if (verbose) {
409			fprintf(stderr,
410				"  handled: %u\n", frame_count);
411		}
412		for (i = 0; i < ctx->cntr_count; ++i) {
413			cntr = &ctx->cntrs[i];
414			if (cntr->eof)
415				break;
416		}
417		if (i < ctx->cntr_count)
418			break;
419
420		*actual_frame_count += frame_count;
421		if (*actual_frame_count >= expected_frame_count)
422			break;
423	}
424
425	if (!ctx->xfer.quiet) {
426		fprintf(stderr,
427			"%s: Expected %" PRIu64 "frames, "
428			"Actual %" PRIu64 "frames\n",
429			snd_pcm_stream_name(direction), expected_frame_count,
430			*actual_frame_count);
431		if (ctx->interrupted) {
432			fprintf(stderr, "Aborted by signal: %s\n",
433			       strsignal(ctx->signal));
434			return 0;
435		}
436	}
437
438	return err;
439}
440
441static void context_post_process(struct context *ctx,
442				 uint64_t accumulated_frame_count ATTRIBUTE_UNUSED)
443{
444	uint64_t total_frame_count;
445	unsigned int i;
446
447	xfer_context_post_process(&ctx->xfer);
448
449	if (ctx->cntrs) {
450		for (i = 0; i < ctx->cntr_count; ++i) {
451			container_context_post_process(ctx->cntrs + i,
452						       &total_frame_count);
453			container_context_destroy(ctx->cntrs + i);
454		}
455		free(ctx->cntrs);
456	}
457
458	if (ctx->cntr_fds) {
459		for (i = 0; i < ctx->cntr_count; ++i)
460			close(ctx->cntr_fds[i]);
461		free(ctx->cntr_fds);
462	}
463
464	mapper_context_post_process(&ctx->mapper);
465	mapper_context_destroy(&ctx->mapper);
466}
467
468static void context_destroy(struct context *ctx)
469{
470	xfer_context_destroy(&ctx->xfer);
471}
472
473int subcmd_transfer(int argc, char *const *argv, snd_pcm_stream_t direction)
474{
475	static struct context ctx = {0};
476	uint64_t expected_frame_count = 0;
477	uint64_t actual_frame_count = 0;
478	int err = 0;
479
480	err = prepare_signal_handler(&ctx);
481	if (err < 0)
482		return err;
483
484	err = context_init(&ctx, direction, argc, argv);
485	if (err < 0)
486		goto end;
487	if (ctx.xfer.help || ctx.xfer.dump_hw_params)
488		goto end;
489
490	err = context_pre_process(&ctx, direction, &expected_frame_count);
491	if (err < 0)
492		goto end;
493
494	err = context_process_frames(&ctx, direction, expected_frame_count,
495				     &actual_frame_count);
496end:
497	context_post_process(&ctx, actual_frame_count);
498
499	context_destroy(&ctx);
500
501	return err;
502}
503