1 /* 2 ** Copyright 2010, The Android Open-Source Project 3 ** Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. 4 ** 5 ** Licensed under the Apache License, Version 2.0 (the "License"); 6 ** you may not use this file except in compliance with the License. 7 ** You may obtain a copy of the License at 8 ** 9 ** http://www.apache.org/licenses/LICENSE-2.0 10 ** 11 ** Unless required by applicable law or agreed to in writing, software 12 ** distributed under the License is distributed on an "AS IS" BASIS, 13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 ** See the License for the specific language governing permissions and 15 ** limitations under the License. 16 */ 17 18 #include <stdio.h> 19 #include <stdlib.h> 20 #include <fcntl.h> 21 #include <unistd.h> 22 #include <stdint.h> 23 #include <string.h> 24 #include <errno.h> 25 #include <sys/poll.h> 26 #include <sys/ioctl.h> 27 #include <getopt.h> 28 29 #include <sound/asound.h> 30 #include "alsa_audio.h" 31 32 #ifndef ANDROID 33 #define strlcat g_strlcat 34 #define strlcpy g_strlcpy 35 #endif 36 37 #define ID_RIFF 0x46464952 38 #define ID_WAVE 0x45564157 39 #define ID_FMT 0x20746d66 40 #define ID_DATA 0x61746164 41 42 #define FORMAT_PCM 1 43 #define LOG_NDEBUG 1 44 static pcm_flag = 1; 45 static debug = 0; 46 static uint32_t play_max_sz = 2147483648LL; 47 static int format = SNDRV_PCM_FORMAT_S16_LE; 48 static int period = 0; 49 static int compressed = 0; 50 static char *compr_codec; 51 static int piped = 0; 52 53 static struct option long_options[] = 54 { 55 {"pcm", 0, 0, 'P'}, 56 {"debug", 0, 0, 'V'}, 57 {"Mmap", 0, 0, 'M'}, 58 {"HW", 1, 0, 'D'}, 59 {"Rate", 1, 0, 'R'}, 60 {"channel", 1, 0, 'C'}, 61 {"format", 1, 0, 'F'}, 62 {"period", 1, 0, 'B'}, 63 {"compressed", 0, 0, 'T'}, 64 {0, 0, 0, 0} 65 }; 66 67 struct wav_header { 68 uint32_t riff_id; 69 uint32_t riff_sz; 70 uint32_t riff_fmt; 71 uint32_t fmt_id; 72 uint32_t fmt_sz; 73 uint16_t audio_format; 74 uint16_t num_channels; 75 uint32_t sample_rate; 76 uint32_t byte_rate; /* sample_rate * num_channels * bps / 8 */ 77 uint16_t block_align; /* num_channels * bps / 8 */ 78 uint16_t bits_per_sample; 79 uint32_t data_id; 80 uint32_t data_sz; 81 }; 82 83 static int set_params(struct pcm *pcm) 84 { 85 struct snd_pcm_hw_params *params; 86 struct snd_pcm_sw_params *sparams; 87 88 unsigned long periodSize, bufferSize, reqBuffSize; 89 unsigned int periodTime, bufferTime; 90 unsigned int requestedRate = pcm->rate; 91 int channels = (pcm->flags & PCM_MONO) ? 1 : ((pcm->flags & PCM_5POINT1)? 6 : 2 ); 92 93 params = (struct snd_pcm_hw_params*) calloc(1, sizeof(struct snd_pcm_hw_params)); 94 if (!params) { 95 fprintf(stderr, "Aplay:Failed to allocate ALSA hardware parameters!"); 96 return -ENOMEM; 97 } 98 99 param_init(params); 100 101 param_set_mask(params, SNDRV_PCM_HW_PARAM_ACCESS, 102 (pcm->flags & PCM_MMAP)? SNDRV_PCM_ACCESS_MMAP_INTERLEAVED : SNDRV_PCM_ACCESS_RW_INTERLEAVED); 103 param_set_mask(params, SNDRV_PCM_HW_PARAM_FORMAT, pcm->format); 104 param_set_mask(params, SNDRV_PCM_HW_PARAM_SUBFORMAT, 105 SNDRV_PCM_SUBFORMAT_STD); 106 if (period) 107 param_set_min(params, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, period); 108 else 109 param_set_min(params, SNDRV_PCM_HW_PARAM_PERIOD_TIME, 10); 110 param_set_int(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, 16); 111 param_set_int(params, SNDRV_PCM_HW_PARAM_FRAME_BITS, 112 pcm->channels * 16); 113 param_set_int(params, SNDRV_PCM_HW_PARAM_CHANNELS, 114 pcm->channels); 115 param_set_int(params, SNDRV_PCM_HW_PARAM_RATE, pcm->rate); 116 param_set_hw_refine(pcm, params); 117 118 if (param_set_hw_params(pcm, params)) { 119 fprintf(stderr, "Aplay:cannot set hw params\n"); 120 return -errno; 121 } 122 if (debug) 123 param_dump(params); 124 125 pcm->buffer_size = pcm_buffer_size(params); 126 pcm->period_size = pcm_period_size(params); 127 pcm->period_cnt = pcm->buffer_size/pcm->period_size; 128 if (debug) { 129 fprintf (stderr,"period_cnt = %d\n", pcm->period_cnt); 130 fprintf (stderr,"period_size = %d\n", pcm->period_size); 131 fprintf (stderr,"buffer_size = %d\n", pcm->buffer_size); 132 } 133 sparams = (struct snd_pcm_sw_params*) calloc(1, sizeof(struct snd_pcm_sw_params)); 134 if (!sparams) { 135 fprintf(stderr, "Aplay:Failed to allocate ALSA software parameters!\n"); 136 return -ENOMEM; 137 } 138 // Get the current software parameters 139 sparams->tstamp_mode = SNDRV_PCM_TSTAMP_NONE; 140 sparams->period_step = 1; 141 142 sparams->avail_min = pcm->period_size/(channels * 2) ; 143 sparams->start_threshold = pcm->period_size/(channels * 2) ; 144 sparams->stop_threshold = pcm->buffer_size ; 145 sparams->xfer_align = pcm->period_size/(channels * 2) ; /* needed for old kernels */ 146 147 sparams->silence_size = 0; 148 sparams->silence_threshold = 0; 149 150 if (param_set_sw_params(pcm, sparams)) { 151 fprintf(stderr, "Aplay:cannot set sw params"); 152 return -errno; 153 } 154 if (debug) { 155 fprintf (stderr,"sparams->avail_min= %lu\n", sparams->avail_min); 156 fprintf (stderr," sparams->start_threshold= %lu\n", sparams->start_threshold); 157 fprintf (stderr," sparams->stop_threshold= %lu\n", sparams->stop_threshold); 158 fprintf (stderr," sparams->xfer_align= %lu\n", sparams->xfer_align); 159 fprintf (stderr," sparams->boundary= %lu\n", sparams->boundary); 160 } 161 return 0; 162 } 163 164 static int play_file(unsigned rate, unsigned channels, int fd, 165 unsigned flags, const char *device, unsigned data_sz) 166 { 167 struct pcm *pcm; 168 struct mixer *mixer; 169 struct pcm_ctl *ctl = NULL; 170 unsigned bufsize; 171 char *data; 172 long avail; 173 long frames; 174 int nfds = 1; 175 struct snd_xferi x; 176 unsigned offset = 0; 177 int err; 178 static int start = 0; 179 struct pollfd pfd[1]; 180 int remainingData = 0; 181 182 flags |= PCM_OUT; 183 184 if (channels == 1) 185 flags |= PCM_MONO; 186 else if (channels == 6) 187 flags |= PCM_5POINT1; 188 else 189 flags |= PCM_STEREO; 190 191 if (debug) 192 flags |= DEBUG_ON; 193 else 194 flags |= DEBUG_OFF; 195 196 pcm = pcm_open(flags, device); 197 if (pcm < 0) 198 return pcm; 199 200 if (!pcm_ready(pcm)) { 201 pcm_close(pcm); 202 return -EBADFD; 203 } 204 205 #ifdef QCOM_COMPRESSED_AUDIO_ENABLED 206 if (compressed) { 207 struct snd_compr_caps compr_cap; 208 struct snd_compr_params compr_params; 209 if (ioctl(pcm->fd, SNDRV_COMPRESS_GET_CAPS, &compr_cap)) { 210 fprintf(stderr, "Aplay: SNDRV_COMPRESS_GET_CAPS, failed Error no %d \n", errno); 211 pcm_close(pcm); 212 return -errno; 213 } 214 if (!period) 215 period = compr_cap.min_fragment_size; 216 switch (get_compressed_format(compr_codec)) { 217 case FORMAT_MP3: 218 compr_params.codec.id = compr_cap.codecs[FORMAT_MP3]; 219 break; 220 case FORMAT_AC3_PASS_THROUGH: 221 compr_params.codec.id = compr_cap.codecs[FORMAT_AC3_PASS_THROUGH]; 222 printf("codec -d = %x\n", compr_params.codec.id); 223 break; 224 default: 225 break; 226 } 227 if (ioctl(pcm->fd, SNDRV_COMPRESS_SET_PARAMS, &compr_params)) { 228 fprintf(stderr, "Aplay: SNDRV_COMPRESS_SET_PARAMS,failed Error no %d \n", errno); 229 pcm_close(pcm); 230 return -errno; 231 } 232 } 233 #endif 234 pcm->channels = channels; 235 pcm->rate = rate; 236 pcm->flags = flags; 237 pcm->format = format; 238 if (set_params(pcm)) { 239 fprintf(stderr, "Aplay:params setting failed\n"); 240 pcm_close(pcm); 241 return -errno; 242 } 243 244 if (!pcm_flag) { 245 if (pcm_prepare(pcm)) { 246 fprintf(stderr, "Aplay:Failed in pcm_prepare\n"); 247 pcm_close(pcm); 248 return -errno; 249 } 250 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_START)) { 251 fprintf(stderr, "Aplay: Hostless IOCTL_START Error no %d \n", errno); 252 pcm_close(pcm); 253 return -errno; 254 } 255 while(1); 256 } 257 258 remainingData = data_sz; 259 260 if (flags & PCM_MMAP) { 261 u_int8_t *dst_addr = NULL; 262 struct snd_pcm_sync_ptr *sync_ptr1 = pcm->sync_ptr; 263 if (mmap_buffer(pcm)) { 264 fprintf(stderr, "Aplay:params setting failed\n"); 265 pcm_close(pcm); 266 return -errno; 267 } 268 if (pcm_prepare(pcm)) { 269 fprintf(stderr, "Aplay:Failed in pcm_prepare\n"); 270 pcm_close(pcm); 271 return -errno; 272 } 273 274 bufsize = pcm->period_size; 275 if (debug) 276 fprintf(stderr, "Aplay:bufsize = %d\n", bufsize); 277 278 pfd[0].fd = pcm->timer_fd; 279 pfd[0].events = POLLIN; 280 281 frames = (pcm->flags & PCM_MONO) ? (bufsize / 2) : (bufsize / 4); 282 for (;;) { 283 if (!pcm->running) { 284 if (pcm_prepare(pcm)) { 285 fprintf(stderr, "Aplay:Failed in pcm_prepare\n"); 286 pcm_close(pcm); 287 return -errno; 288 } 289 pcm->running = 1; 290 start = 0; 291 } 292 /* Sync the current Application pointer from the kernel */ 293 pcm->sync_ptr->flags = SNDRV_PCM_SYNC_PTR_APPL | SNDRV_PCM_SYNC_PTR_AVAIL_MIN;//SNDRV_PCM_SYNC_PTR_HWSYNC; 294 err = sync_ptr(pcm); 295 if (err == EPIPE) { 296 fprintf(stderr, "Aplay:Failed in sync_ptr \n"); 297 /* we failed to make our window -- try to restart */ 298 pcm->underruns++; 299 pcm->running = 0; 300 continue; 301 } 302 /* 303 * Check for the available buffer in driver. If available buffer is 304 * less than avail_min we need to wait 305 */ 306 avail = pcm_avail(pcm); 307 if (avail < 0) { 308 fprintf(stderr, "Aplay:Failed in pcm_avail\n"); 309 pcm_close(pcm); 310 return avail; 311 } 312 if (avail < pcm->sw_p->avail_min) { 313 poll(pfd, nfds, TIMEOUT_INFINITE); 314 continue; 315 } 316 /* 317 * Now that we have buffer size greater than avail_min available to 318 * to be written we need to calcutate the buffer offset where we can 319 * start writting. 320 */ 321 dst_addr = dst_address(pcm); 322 323 if (debug) { 324 fprintf(stderr, "dst_addr = 0x%08x\n", dst_addr); 325 fprintf(stderr, "Aplay:avail = %d frames = %d\n",avail, frames); 326 fprintf(stderr, "Aplay:sync_ptr->s.status.hw_ptr %ld pcm->buffer_size %d sync_ptr->c.control.appl_ptr %ld\n", 327 pcm->sync_ptr->s.status.hw_ptr, 328 pcm->buffer_size, 329 pcm->sync_ptr->c.control.appl_ptr); 330 } 331 332 /* 333 * Read from the file to the destination buffer in kernel mmaped buffer 334 * This reduces a extra copy of intermediate buffer. 335 */ 336 memset(dst_addr, 0x0, bufsize); 337 338 if (data_sz && !piped) { 339 if (remainingData < bufsize) { 340 bufsize = remainingData; 341 frames = (pcm->flags & PCM_MONO) ? (remainingData / 2) : (remainingData / 4); 342 } 343 } 344 345 err = read(fd, dst_addr , bufsize); 346 if (debug) 347 fprintf(stderr, "read %d bytes from file\n", err); 348 if (err <= 0) 349 break; 350 351 if (data_sz && !piped) { 352 remainingData -= bufsize; 353 if (remainingData <= 0) 354 break; 355 } 356 357 /* 358 * Increment the application pointer with data written to kernel. 359 * Update kernel with the new sync pointer. 360 */ 361 pcm->sync_ptr->c.control.appl_ptr += frames; 362 pcm->sync_ptr->flags = 0; 363 364 err = sync_ptr(pcm); 365 if (err == EPIPE) { 366 fprintf(stderr, "Aplay:Failed in sync_ptr 2 \n"); 367 /* we failed to make our window -- try to restart */ 368 pcm->underruns++; 369 pcm->running = 0; 370 continue; 371 } 372 373 if (debug) { 374 fprintf(stderr, "Aplay:sync_ptr->s.status.hw_ptr %ld sync_ptr->c.control.appl_ptr %ld\n", 375 pcm->sync_ptr->s.status.hw_ptr, 376 pcm->sync_ptr->c.control.appl_ptr); 377 #ifdef QCOM_COMPRESSED_AUDIO_ENABLED 378 if (compressed && start) { 379 struct snd_compr_tstamp tstamp; 380 if (ioctl(pcm->fd, SNDRV_COMPRESS_TSTAMP, &tstamp)) 381 fprintf(stderr, "Aplay: failed SNDRV_COMPRESS_TSTAMP\n"); 382 else 383 fprintf(stderr, "timestamp = %lld\n", tstamp.timestamp); 384 } 385 #endif 386 } 387 /* 388 * If we have reached start threshold of buffer prefill, 389 * its time to start the driver. 390 */ 391 if(start) 392 goto start_done; 393 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_START)) { 394 err = -errno; 395 if (errno == EPIPE) { 396 fprintf(stderr, "Aplay:Failed in SNDRV_PCM_IOCTL_START\n"); 397 /* we failed to make our window -- try to restart */ 398 pcm->underruns++; 399 pcm->running = 0; 400 continue; 401 } else { 402 fprintf(stderr, "Aplay:Error no %d \n", errno); 403 pcm_close(pcm); 404 return -errno; 405 } 406 } else 407 start = 1; 408 409 start_done: 410 offset += frames; 411 } 412 while(1) { 413 pcm->sync_ptr->flags = SNDRV_PCM_SYNC_PTR_APPL | SNDRV_PCM_SYNC_PTR_AVAIL_MIN;//SNDRV_PCM_SYNC_PTR_HWSYNC; 414 sync_ptr(pcm); 415 /* 416 * Check for the available buffer in driver. If available buffer is 417 * less than avail_min we need to wait 418 */ 419 if (pcm->sync_ptr->s.status.hw_ptr >= pcm->sync_ptr->c.control.appl_ptr) { 420 fprintf(stderr, "Aplay:sync_ptr->s.status.hw_ptr %ld sync_ptr->c.control.appl_ptr %ld\n", 421 pcm->sync_ptr->s.status.hw_ptr, 422 pcm->sync_ptr->c.control.appl_ptr); 423 break; 424 } else 425 poll(pfd, nfds, TIMEOUT_INFINITE); 426 } 427 } else { 428 if (pcm_prepare(pcm)) { 429 fprintf(stderr, "Aplay:Failed in pcm_prepare\n"); 430 pcm_close(pcm); 431 return -errno; 432 } 433 434 bufsize = pcm->period_size; 435 436 data = calloc(1, bufsize); 437 if (!data) { 438 fprintf(stderr, "Aplay:could not allocate %d bytes\n", bufsize); 439 pcm_close(pcm); 440 return -ENOMEM; 441 } 442 443 if (data_sz && !piped) { 444 if (remainingData < bufsize) 445 bufsize = remainingData; 446 } 447 448 while (read(fd, data, bufsize) > 0) { 449 if (pcm_write(pcm, data, bufsize)){ 450 fprintf(stderr, "Aplay: pcm_write failed\n"); 451 free(data); 452 pcm_close(pcm); 453 return -errno; 454 } 455 memset(data, 0, bufsize); 456 457 if (data_sz && !piped) { 458 remainingData -= bufsize; 459 if (remainingData <= 0) 460 break; 461 if (remainingData < bufsize) 462 bufsize = remainingData; 463 } 464 } 465 free(data); 466 } 467 fprintf(stderr, "Aplay: Done playing\n"); 468 pcm_close(pcm); 469 return 0; 470 } 471 472 int play_raw(const char *fg, int rate, int ch, const char *device, const char *fn) 473 { 474 int fd; 475 unsigned flag = 0; 476 477 if(!fn) { 478 fd = fileno(stdin); 479 piped = 1; 480 } else { 481 fd = open(fn, O_RDONLY); 482 if (fd < 0) { 483 fprintf(stderr, "Aplay:aplay: cannot open '%s'\n", fn); 484 return fd; 485 } 486 } 487 488 if (!strncmp(fg, "M", sizeof("M"))) 489 flag = PCM_MMAP; 490 else if (!strncmp(fg, "N", sizeof("N"))) 491 flag = PCM_NMMAP; 492 493 fprintf(stderr, "aplay: Playing '%s': format %s ch = %d\n", 494 fn, get_format_desc(format), ch ); 495 return play_file(rate, ch, fd, flag, device, 0); 496 } 497 498 int play_wav(const char *fg, int rate, int ch, const char *device, const char *fn) 499 { 500 struct wav_header hdr; 501 int fd; 502 unsigned flag = 0; 503 504 if (pcm_flag) { 505 if(!fn) { 506 fd = fileno(stdin); 507 piped = 1; 508 } else { 509 fd = open(fn, O_RDONLY); 510 if (fd < 0) { 511 fprintf(stderr, "Aplay:aplay: cannot open '%s'\n", fn); 512 return fd; 513 } 514 } 515 if (compressed) { 516 hdr.sample_rate = rate; 517 hdr.num_channels = ch; 518 hdr.data_sz = 0; 519 goto ignore_header; 520 } 521 522 if (read(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) { 523 fprintf(stderr, "Aplay:aplay: cannot read header\n"); 524 return -errno; 525 } 526 527 if ((hdr.riff_id != ID_RIFF) || 528 (hdr.riff_fmt != ID_WAVE) || 529 (hdr.fmt_id != ID_FMT)) { 530 fprintf(stderr, "Aplay:aplay: '%s' is not a riff/wave file\n", fn); 531 return -EINVAL; 532 } 533 if ((hdr.audio_format != FORMAT_PCM) || 534 (hdr.fmt_sz != 16)) { 535 fprintf(stderr, "Aplay:aplay: '%s' is not pcm format\n", fn); 536 return -EINVAL; 537 } 538 if (hdr.bits_per_sample != 16) { 539 fprintf(stderr, "Aplay:aplay: '%s' is not 16bit per sample\n", fn); 540 return -EINVAL; 541 } 542 } else { 543 fd = -EBADFD; 544 hdr.sample_rate = rate; 545 hdr.num_channels = ch; 546 hdr.data_sz = 0; 547 } 548 549 ignore_header: 550 if (!strncmp(fg, "M", sizeof("M"))) 551 flag = PCM_MMAP; 552 else if (!strncmp(fg, "N", sizeof("N"))) 553 flag = PCM_NMMAP; 554 fprintf(stderr, "aplay: Playing '%s':%s\n", fn, get_format_desc(format) ); 555 556 return play_file(hdr.sample_rate, hdr.num_channels, fd, flag, device, hdr.data_sz); 557 } 558 559 int main(int argc, char **argv) 560 { 561 int option_index = 0; 562 int c,i; 563 int ch = 2; 564 int rate = 44100; 565 char *mmap = "N"; 566 char *device = "hw:0,0"; 567 char *filename; 568 int rc = 0; 569 570 if (argc <2) { 571 printf("\nUsage: aplay [options] <file>\n" 572 "options:\n" 573 "-D <hw:C,D> -- Alsa PCM by name\n" 574 "-M -- Mmap stream\n" 575 "-P -- Hostless steam[No PCM]\n" 576 "-C -- Channels\n" 577 "-R -- Rate\n" 578 "-V -- verbose\n" 579 "-F -- Format\n" 580 "-B -- Period\n" 581 "-T <MP3, AAC, AC3_PASS_THROUGH> -- Compressed\n" 582 "<file> \n"); 583 fprintf(stderr, "Formats Supported:\n"); 584 for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; ++i) 585 if (get_format_name(i)) 586 fprintf(stderr, "%s ", get_format_name(i)); 587 fprintf(stderr, "\nSome of these may not be available on selected hardware\n"); 588 return 0; 589 } 590 while ((c = getopt_long(argc, argv, "PVMD:R:C:F:B:T:", long_options, &option_index)) != -1) { 591 switch (c) { 592 case 'P': 593 pcm_flag = 0; 594 break; 595 case 'V': 596 debug = 1; 597 break; 598 case 'M': 599 mmap = "M"; 600 break; 601 case 'D': 602 device = optarg; 603 break; 604 case 'R': 605 rate = (int)strtol(optarg, NULL, 0); 606 break; 607 case 'C': 608 ch = (int)strtol(optarg, NULL, 0); 609 break; 610 case 'F': 611 printf("optarg = %s\n", optarg); 612 format = get_format(optarg); 613 break; 614 case 'B': 615 period = (int)strtol(optarg, NULL, 0); 616 break; 617 case 'T': 618 compressed = 1; 619 printf("compressed codec type requested = %s\n", optarg); 620 compr_codec = optarg; 621 break; 622 default: 623 printf("\nUsage: aplay [options] <file>\n" 624 "options:\n" 625 "-D <hw:C,D> -- Alsa PCM by name\n" 626 "-M -- Mmap stream\n" 627 "-P -- Hostless steam[No PCM]\n" 628 "-V -- verbose\n" 629 "-C -- Channels\n" 630 "-R -- Rate\n" 631 "-F -- Format\n" 632 "-B -- Period\n" 633 "-T -- Compressed\n" 634 "<file> \n"); 635 fprintf(stderr, "Formats Supported:\n"); 636 for (i = 0; i < SNDRV_PCM_FORMAT_LAST; ++i) 637 if (get_format_name(i)) 638 fprintf(stderr, "%s ", get_format_name(i)); 639 fprintf(stderr, "\nSome of these may not be available on selected hardware\n"); 640 return -EINVAL; 641 } 642 643 } 644 filename = (char*) calloc(1, 30); 645 if (!filename) { 646 fprintf(stderr, "Aplay:Failed to allocate filename!"); 647 return -ENOMEM; 648 } 649 if (optind > argc - 1) { 650 free(filename); 651 filename = NULL; 652 } else { 653 strlcpy(filename, argv[optind++], 30); 654 } 655 656 if (pcm_flag) { 657 if (format == SNDRV_PCM_FORMAT_S16_LE) 658 rc = play_wav(mmap, rate, ch, device, filename); 659 else 660 rc = play_raw(mmap, rate, ch, device, filename); 661 } else { 662 rc = play_wav(mmap, rate, ch, device, "dummy"); 663 } 664 if (filename) 665 free(filename); 666 667 return rc; 668 } 669 670