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