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