• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
16 struct 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.
30 static struct context *ctx_ptr;
31 
handle_unix_signal_for_finish(int sig)32 static void handle_unix_signal_for_finish(int sig)
33 {
34 	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 
handle_unix_signal_for_suspend(int sig)43 static void handle_unix_signal_for_suspend(int sig)
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 
prepare_signal_handler(struct context * ctx)98 static 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 
context_init(struct context * ctx,snd_pcm_stream_t direction,int argc,char * const * argv)122 static 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 
allocate_containers(struct context * ctx,unsigned int count)151 static 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 
capture_pre_process(struct context * ctx,snd_pcm_access_t * access,snd_pcm_uframes_t * frames_per_buffer,uint64_t * total_frame_count)165 static 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 	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 
playback_pre_process(struct context * ctx,snd_pcm_access_t * access,snd_pcm_uframes_t * frames_per_buffer,uint64_t * total_frame_count)229 static 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 	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 
context_pre_process(struct context * ctx,snd_pcm_stream_t direction,uint64_t * total_frame_count)328 static 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 
context_process_frames(struct context * ctx,snd_pcm_stream_t direction,uint64_t expected_frame_count,uint64_t * actual_frame_count)370 static 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 	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 
context_post_process(struct context * ctx,uint64_t accumulated_frame_count)441 static void context_post_process(struct context *ctx,
442 				 uint64_t accumulated_frame_count)
443 {
444 	uint64_t total_frame_count;
445 	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 
context_destroy(struct context * ctx)468 static void context_destroy(struct context *ctx)
469 {
470 	xfer_context_destroy(&ctx->xfer);
471 }
472 
subcmd_transfer(int argc,char * const * argv,snd_pcm_stream_t direction)473 int subcmd_transfer(int argc, char *const *argv, snd_pcm_stream_t direction)
474 {
475 	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);
496 end:
497 	context_post_process(&ctx, actual_frame_count);
498 
499 	context_destroy(&ctx);
500 
501 	return err;
502 }
503