Home | History | Annotate | Download | only in libalsa-intf
      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 <signal.h>
     25 #include <errno.h>
     26 #include <sys/poll.h>
     27 #include <sys/ioctl.h>
     28 #include <getopt.h>
     29 #include <limits.h>
     30 
     31 #include "alsa_audio.h"
     32 
     33 #define ID_RIFF 0x46464952
     34 #define ID_WAVE 0x45564157
     35 #define ID_FMT  0x20746d66
     36 #define ID_DATA 0x61746164
     37 
     38 #define FORMAT_PCM 1
     39 
     40 #ifndef ANDROID
     41 #define strlcat g_strlcat
     42 #define strlcpy g_strlcpy
     43 #endif
     44 
     45 static struct wav_header hdr;
     46 static int fd;
     47 static struct pcm *pcm;
     48 static int debug = 0;
     49 static int pcm_flag = 1;
     50 static int duration = 0;
     51 static char *filename;
     52 static char *data;
     53 static int format = SNDRV_PCM_FORMAT_S16_LE;
     54 static int period = 0;
     55 static int piped = 0;
     56 
     57 static struct option long_options[] =
     58 {
     59     {"pcm", 0, 0, 'P'},
     60     {"debug", 0, 0, 'V'},
     61     {"Mmap", 0, 0, 'M'},
     62     {"HW", 1, 0, 'D'},
     63     {"Rate", 1, 0, 'R'},
     64     {"channel", 1, 0, 'C'},
     65     {"duration", 1, 0, 'T'},
     66     {"format", 1, 0, 'F'},
     67     {"period", 1, 0, 'B'},
     68     {0, 0, 0, 0}
     69 };
     70 
     71 struct wav_header {
     72     uint32_t riff_id;
     73     uint32_t riff_sz;
     74     uint32_t riff_fmt;
     75     uint32_t fmt_id;
     76     uint32_t fmt_sz;
     77     uint16_t audio_format;
     78     uint16_t num_channels;
     79     uint32_t sample_rate;
     80     uint32_t byte_rate;       /* sample_rate * num_channels * bps / 8 */
     81     uint16_t block_align;     /* num_channels * bps / 8 */
     82     uint16_t bits_per_sample;
     83     uint32_t data_id;
     84     uint32_t data_sz;
     85 };
     86 
     87 static int set_params(struct pcm *pcm)
     88 {
     89      struct snd_pcm_hw_params *params;
     90      struct snd_pcm_sw_params *sparams;
     91 
     92      unsigned long periodSize, bufferSize, reqBuffSize;
     93      unsigned int periodTime, bufferTime;
     94      unsigned int requestedRate = pcm->rate;
     95 
     96      params = (struct snd_pcm_hw_params*) calloc(1, sizeof(struct snd_pcm_hw_params));
     97      if (!params) {
     98           fprintf(stderr, "Arec:Failed to allocate ALSA hardware parameters!");
     99           return -ENOMEM;
    100      }
    101 
    102      param_init(params);
    103 
    104      param_set_mask(params, SNDRV_PCM_HW_PARAM_ACCESS,
    105                     (pcm->flags & PCM_MMAP)? SNDRV_PCM_ACCESS_MMAP_INTERLEAVED : SNDRV_PCM_ACCESS_RW_INTERLEAVED);
    106      param_set_mask(params, SNDRV_PCM_HW_PARAM_FORMAT, pcm->format);
    107      param_set_mask(params, SNDRV_PCM_HW_PARAM_SUBFORMAT,
    108                     SNDRV_PCM_SUBFORMAT_STD);
    109      if (period)
    110          param_set_min(params, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, period);
    111      else
    112          param_set_min(params, SNDRV_PCM_HW_PARAM_PERIOD_TIME, 10);
    113      param_set_int(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, 16);
    114      param_set_int(params, SNDRV_PCM_HW_PARAM_FRAME_BITS,
    115                     pcm->channels * 16);
    116      param_set_int(params, SNDRV_PCM_HW_PARAM_CHANNELS,
    117                     pcm->channels);
    118      param_set_int(params, SNDRV_PCM_HW_PARAM_RATE, pcm->rate);
    119 
    120      param_set_hw_refine(pcm, params);
    121 
    122      if (param_set_hw_params(pcm, params)) {
    123          fprintf(stderr, "Arec:cannot set hw params");
    124          return -errno;
    125      }
    126      if (debug)
    127           param_dump(params);
    128 
    129      pcm->buffer_size = pcm_buffer_size(params);
    130      pcm->period_size = pcm_period_size(params);
    131      pcm->period_cnt = pcm->buffer_size/pcm->period_size;
    132      if (debug) {
    133         fprintf (stderr,"period_size (%d)", pcm->period_size);
    134         fprintf (stderr," buffer_size (%d)", pcm->buffer_size);
    135         fprintf (stderr," period_cnt  (%d)\n", pcm->period_cnt);
    136      }
    137      sparams = (struct snd_pcm_sw_params*) calloc(1, sizeof(struct snd_pcm_sw_params));
    138      if (!sparams) {
    139          fprintf(stderr, "Arec:Failed to allocate ALSA software parameters!\n");
    140          return -ENOMEM;
    141      }
    142     sparams->tstamp_mode = SNDRV_PCM_TSTAMP_NONE;
    143     sparams->period_step = 1;
    144 
    145     if (pcm->flags & PCM_MONO) {
    146         sparams->avail_min = pcm->period_size/2;
    147         sparams->xfer_align = pcm->period_size/2;
    148     } else if (pcm->flags & PCM_QUAD) {
    149         sparams->avail_min = pcm->period_size/8;
    150         sparams->xfer_align = pcm->period_size/8;
    151     } else if (pcm->flags & PCM_5POINT1) {
    152         sparams->avail_min = pcm->period_size/12;
    153         sparams->xfer_align = pcm->period_size/12;
    154     } else {
    155         sparams->avail_min = pcm->period_size/4;
    156         sparams->xfer_align = pcm->period_size/4;
    157     }
    158 
    159     sparams->start_threshold = 1;
    160     sparams->stop_threshold = INT_MAX;
    161     sparams->silence_size = 0;
    162     sparams->silence_threshold = 0;
    163 
    164     if (param_set_sw_params(pcm, sparams)) {
    165          fprintf(stderr, "Arec:cannot set sw params");
    166          return -errno;
    167     }
    168     if (debug) {
    169         fprintf (stderr,"avail_min (%lu)\n", sparams->avail_min);
    170         fprintf (stderr,"start_threshold (%lu)\n", sparams->start_threshold);
    171         fprintf (stderr,"stop_threshold (%lu)\n", sparams->stop_threshold);
    172         fprintf (stderr,"xfer_align (%lu)\n", sparams->xfer_align);
    173     }
    174     return 0;
    175 
    176 }
    177 
    178 int record_file(unsigned rate, unsigned channels, int fd, unsigned count,  unsigned flags, const char *device)
    179 {
    180     unsigned xfer, bufsize;
    181     int r, avail;
    182     int nfds = 1;
    183     static int start = 0;
    184     struct snd_xferi x;
    185     long frames;
    186     unsigned offset = 0;
    187     int err;
    188     struct pollfd pfd[1];
    189     int rec_size = 0;
    190 
    191     flags |= PCM_IN;
    192 
    193     if (channels == 1)
    194         flags |= PCM_MONO;
    195     else if (channels == 4)
    196         flags |= PCM_QUAD;
    197     else if (channels == 6)
    198         flags |= PCM_5POINT1;
    199     else
    200         flags |= PCM_STEREO;
    201 
    202     pcm = pcm_open(flags, device);
    203     if (!pcm_ready(pcm)) {
    204         pcm_close(pcm);
    205         goto fail;
    206     }
    207     pcm->channels = channels;
    208     pcm->rate = rate;
    209     pcm->flags = flags;
    210     pcm->format = format;
    211     if (set_params(pcm)) {
    212         fprintf(stderr, "Arec:params setting failed\n");
    213         pcm_close(pcm);
    214         return -EINVAL;
    215     }
    216 
    217     if (!pcm_flag) {
    218         if (pcm_prepare(pcm)) {
    219             fprintf(stderr, "Arec:Failed in pcm_prepare\n");
    220             pcm_close(pcm);
    221             return -errno;
    222         }
    223 	if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_START)) {
    224             fprintf(stderr, "Arec: Hostless IOCTL_START Error no %d \n", errno);
    225             pcm_close(pcm);
    226             return -errno;
    227 	}
    228         while(1);
    229    }
    230 
    231     if (flags & PCM_MMAP) {
    232         u_int8_t *dst_addr = NULL;
    233         struct snd_pcm_sync_ptr *sync_ptr1 = pcm->sync_ptr;
    234         unsigned int tmp;
    235 
    236         if (mmap_buffer(pcm)) {
    237              fprintf(stderr, "Arec:params setting failed\n");
    238              pcm_close(pcm);
    239              return -EINVAL;
    240         }
    241         if (debug)
    242             fprintf(stderr, "Arec:mmap_buffer done\n");
    243 
    244         if (pcm_prepare(pcm)) {
    245             fprintf(stderr, "Arec:Failed in pcm_prepare\n");
    246             pcm_close(pcm);
    247             return -errno;
    248         }
    249 
    250         bufsize = pcm->period_size;
    251         if (debug)
    252 	    fprintf(stderr, "Arec:bufsize = %d\n", bufsize);
    253         if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_START)) {
    254 		if (errno == EPIPE) {
    255 			fprintf(stderr, "Arec:Failed in SNDRV_PCM_IOCTL_START\n");
    256 			/* we failed to make our window -- try to restart */
    257 			pcm->running = 0;
    258 		} else {
    259 			fprintf(stderr, "Arec:Error no %d \n", errno);
    260 			return -errno;
    261 		}
    262         }
    263 
    264         pfd[0].fd = pcm->fd;
    265         pfd[0].events = POLLIN;
    266 
    267         hdr.data_sz = 0;
    268         if (pcm->flags & PCM_MONO) {
    269                 frames = bufsize / 2;
    270         } else if (pcm->flags & PCM_QUAD) {
    271                 frames = bufsize / 8;
    272         } else if (pcm->flags & PCM_5POINT1) {
    273                 frames = bufsize / 12;
    274         } else{
    275                 frames = bufsize / 4;
    276         }
    277         x.frames = frames;
    278         for(;;) {
    279 		if (!pcm->running) {
    280                     if (pcm_prepare(pcm))
    281                         return --errno;
    282                     start = 0;
    283                 }
    284                 /* Sync the current Application pointer from the kernel */
    285 		pcm->sync_ptr->flags = SNDRV_PCM_SYNC_PTR_APPL | SNDRV_PCM_SYNC_PTR_AVAIL_MIN;//SNDRV_PCM_SYNC_PTR_HWSYNC;
    286                 err = sync_ptr(pcm);
    287                 if (err == EPIPE) {
    288                      fprintf(stderr, "Arec:Failed in sync_ptr \n");
    289                      /* we failed to make our window -- try to restart */
    290                      //pcm->overruns++;
    291                      pcm->running = 0;
    292                      continue;
    293                 }
    294                /*
    295                 * Check for the available data in driver. If available data is
    296                 * less than avail_min we need to wait
    297                 */
    298                 avail = pcm_avail(pcm);
    299                 if (debug)
    300                      fprintf(stderr, "Arec:avail 1 = %d frames = %ld\n",avail, frames);
    301                 if (avail < 0)
    302                         return avail;
    303                 if (avail < pcm->sw_p->avail_min) {
    304                         poll(pfd, nfds, TIMEOUT_INFINITE);
    305                         continue;
    306                 }
    307 	 	if (x.frames > avail)
    308                         frames = avail;
    309                /*
    310                 * Now that we have data size greater than avail_min available to
    311                 * to be read we need to calcutate the buffer offset where we can
    312                 * start reading from.
    313                 */
    314                 dst_addr = dst_address(pcm);
    315 
    316                /*
    317                 * Write to the file at the destination address from kernel mmaped buffer
    318                 * This reduces a extra copy of intermediate buffer.
    319                 */
    320                 if (write(fd, dst_addr, bufsize) != bufsize) {
    321                     fprintf(stderr, "Arec:could not write %d bytes\n", bufsize);
    322                     return -errno;
    323                 }
    324                 x.frames -= frames;
    325                 pcm->sync_ptr->c.control.appl_ptr += frames;
    326 		pcm->sync_ptr->flags = 0;
    327                 err = sync_ptr(pcm);
    328                 if (err == EPIPE) {
    329                      fprintf(stderr, "Arec:Failed in sync_ptr \n");
    330                      /* we failed to make our window -- try to restart */
    331                      pcm->running = 0;
    332                      continue;
    333                 }
    334                 rec_size += bufsize;
    335                 hdr.data_sz += bufsize;
    336                 hdr.riff_sz = hdr.data_sz + 44 - 8;
    337                 if (!piped) {
    338                     lseek(fd, 0, SEEK_SET);
    339                     write(fd, &hdr, sizeof(hdr));
    340                     lseek(fd, 0, SEEK_END);
    341                 }
    342                 if (rec_size >= count)
    343                       break;
    344            }
    345     } else {
    346 	    bufsize = pcm->period_size;
    347             if (pcm_prepare(pcm)) {
    348                 fprintf(stderr, "Arec:Failed in pcm_prepare\n");
    349                 pcm_close(pcm);
    350                 return -errno;
    351             }
    352 
    353 	    data = calloc(1, bufsize);
    354 	    if (!data) {
    355 		fprintf(stderr, "Arec:could not allocate %d bytes\n", bufsize);
    356 		return -ENOMEM;
    357 	    }
    358 
    359 	    while (!pcm_read(pcm, data, bufsize)) {
    360 		if (write(fd, data, bufsize) != bufsize) {
    361 		    fprintf(stderr, "Arec:could not write %d bytes\n", bufsize);
    362 		    break;
    363 		}
    364                 rec_size += bufsize;
    365                 hdr.data_sz += bufsize;
    366                 hdr.riff_sz = hdr.data_sz + 44 - 8;
    367                 if (!piped) {
    368                     lseek(fd, 0, SEEK_SET);
    369                     write(fd, &hdr, sizeof(hdr));
    370                     lseek(fd, 0, SEEK_END);
    371                 }
    372                 if (rec_size >= count)
    373                     break;
    374 	    }
    375     }
    376     fprintf(stderr, " rec_size =%d count =%d\n", rec_size, count);
    377     close(fd);
    378     free(data);
    379     pcm_close(pcm);
    380     return hdr.data_sz;
    381 
    382 fail:
    383     fprintf(stderr, "Arec:pcm error: %s\n", pcm_error(pcm));
    384     return -errno;
    385 }
    386 
    387 int rec_raw(const char *fg, const char *device, int rate, int ch,
    388                     const char *fn)
    389 {
    390     unsigned flag = 0;
    391     uint32_t rec_max_sz = 2147483648LL;
    392     uint32_t count;
    393     int i = 0;
    394 
    395     if (!fn) {
    396         fd = fileno(stdout);
    397         piped = 1;
    398     } else {
    399         fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0664);
    400         if (fd < 0) {
    401             fprintf(stderr, "Arec:arec: cannot open '%s'\n", fn);
    402             return -EBADFD;
    403         }
    404     }
    405     if (duration == 0) {
    406          count = rec_max_sz;
    407     } else {
    408          count = rate * ch * 2;
    409          count *= (uint32_t)duration;
    410     }
    411     count = count < rec_max_sz ? count : rec_max_sz;
    412     if (debug)
    413         fprintf(stderr, "arec: %d ch, %d hz, %d bit, format %x\n",
    414         ch, rate, 16, format);
    415 
    416     if (!strncmp(fg, "M", sizeof("M"))) {
    417         flag = PCM_MMAP;
    418     } else if (!strncmp(fg, "N", sizeof("N"))) {
    419         flag = PCM_NMMAP;
    420     }
    421     return record_file(rate, ch, fd, count, flag, device);
    422 }
    423 
    424 int rec_wav(const char *fg, const char *device, int rate, int ch, const char *fn)
    425 {
    426     unsigned flag = 0;
    427     uint32_t rec_max_sz = 2147483648LL;
    428     uint32_t count = 0;
    429     int i = 0;
    430 
    431     if (pcm_flag) {
    432             if (!fn) {
    433               fd = fileno(stdout);
    434               piped = 1;
    435             } else {
    436 	       fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0664);
    437 	       if (fd < 0) {
    438 	            fprintf(stderr, "Arec:arec: cannot open '%s'\n", fn);
    439 		    return -EBADFD;
    440 	       }
    441             }
    442 	    memset(&hdr, 0, sizeof(struct wav_header));
    443 	    hdr.riff_id = ID_RIFF;
    444 	    hdr.riff_fmt = ID_WAVE;
    445 	    hdr.fmt_id = ID_FMT;
    446 	    hdr.fmt_sz = 16;
    447 	    hdr.audio_format = FORMAT_PCM;
    448 	    hdr.num_channels = ch;
    449 	    hdr.sample_rate = rate;
    450             hdr.bits_per_sample = 16;
    451             hdr.byte_rate = (rate * ch * hdr.bits_per_sample) / 8;
    452             hdr.block_align = ( hdr.bits_per_sample * ch ) / 8;
    453 	    hdr.data_id = ID_DATA;
    454 	    hdr.data_sz = 0;
    455 
    456             if (duration == 0) {
    457                 count = rec_max_sz;
    458             } else {
    459                 count = rate * ch * 2;
    460                 count *= (uint32_t)duration;
    461             }
    462             hdr.riff_sz = hdr.data_sz + 44 - 8;
    463 	    if (write(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) {
    464 		if (debug)
    465 		    fprintf(stderr, "arec: cannot write header\n");
    466 		return -errno;
    467 	    }
    468 	    if (debug)
    469 		fprintf(stderr, "arec: %d ch, %d hz, %d bit, %s\n",
    470 		    hdr.num_channels, hdr.sample_rate, hdr.bits_per_sample,
    471 		    hdr.audio_format == FORMAT_PCM ? "PCM" : "unknown");
    472     } else {
    473             hdr.sample_rate = rate;
    474             hdr.num_channels = ch;
    475     }
    476 
    477     if (!strncmp(fg, "M", sizeof("M"))) {
    478         flag = PCM_MMAP;
    479     } else if (!strncmp(fg, "N", sizeof("N"))) {
    480         flag = PCM_NMMAP;
    481     }
    482     return record_file(hdr.sample_rate, hdr.num_channels, fd, count, flag, device);
    483 }
    484 
    485 static void signal_handler(int sig)
    486 {
    487     long file_size;
    488     FILE *fp;
    489 
    490     fprintf(stderr, "Arec:Aborted by signal %s...\n", strsignal(sig));
    491     fprintf(stderr, "Arec:lseeked to %d", (int) lseek(fd, 0, SEEK_SET));
    492     hdr.riff_sz = hdr.data_sz + 44 - 8;
    493     fprintf(stderr, "Arec: hdr.data_sz =%d\n", hdr.data_sz);
    494     fprintf(stderr, "Arec: hdr.riff_sz =%d\n", hdr.riff_sz);
    495     if (write(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) {
    496 	if (debug)
    497             fprintf(stderr, "Arec:arec: cannot write header\n");
    498     } else
    499        fd = -1;
    500 
    501     if (fd > 1) {
    502         close(fd);
    503         fd = -1;
    504     }
    505     free(filename);
    506     free(data);
    507     pcm = NULL;
    508     raise(sig);
    509 }
    510 
    511 int main(int argc, char **argv)
    512 {
    513     int rate = 48000;
    514     int ch = 1;
    515     int i = 0;
    516     int option_index = 0;
    517     int c;
    518     char *mmap = "N";
    519     char *device = "hw:0,0";
    520     struct sigaction sa;
    521     int rc = 0;
    522 
    523     if (argc < 2) {
    524           printf("\nUsage: arec [options] <file>\n"
    525                 "options:\n"
    526                 "-D <hw:C,D>	-- Alsa PCM by name\n"
    527                 "-M		-- Mmap stream\n"
    528                 "-P		-- Hostless steam[No PCM]\n"
    529                 "-V		-- verbose\n"
    530                 "-C		-- Channels\n"
    531                 "-R		-- Rate\n"
    532                 "-T		-- Time in seconds for recording\n"
    533 		"-F             -- Format\n"
    534                 "-B             -- Period\n"
    535                 "<file> \n");
    536            for (i = 0; i < SNDRV_PCM_FORMAT_LAST; ++i)
    537                if (get_format_name(i))
    538                    fprintf(stderr, "%s ", get_format_name(i));
    539            fprintf(stderr, "\nSome of these may not be available on selected hardware\n");
    540           return 0;
    541     }
    542     while ((c = getopt_long(argc, argv, "PVMD:R:C:T:F:B:", long_options, &option_index)) != -1) {
    543        switch (c) {
    544        case 'P':
    545           pcm_flag = 0;
    546           break;
    547        case 'V':
    548           debug = 1;
    549           break;
    550        case 'M':
    551           mmap = "M";
    552           break;
    553        case 'D':
    554           device = optarg;
    555           break;
    556        case 'R':
    557           rate = (int)strtol(optarg, NULL, 0);
    558           break;
    559        case 'C':
    560           ch  = (int)strtol(optarg, NULL, 0);
    561           break;
    562        case 'T':
    563           duration = (int)strtol(optarg, NULL, 0);
    564           break;
    565        case 'F':
    566           format = (int)get_format(optarg);
    567           break;
    568        case 'B':
    569           period = (int)strtol(optarg, NULL, 0);
    570           break;
    571        default:
    572           printf("\nUsage: arec [options] <file>\n"
    573                 "options:\n"
    574                 "-D <hw:C,D>	-- Alsa PCM by name\n"
    575                 "-M		-- Mmap stream\n"
    576                 "-P		-- Hostless steam[No PCM]\n"
    577                 "-V		-- verbose\n"
    578                 "-C		-- Channels\n"
    579                 "-R		-- Rate\n"
    580                 "-T		-- Time in seconds for recording\n"
    581 		"-F             -- Format\n"
    582                 "-B             -- Period\n"
    583                 "<file> \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 -EINVAL;
    589        }
    590     }
    591     filename = (char*) calloc(1, 30);
    592      if (!filename) {
    593           fprintf(stderr, "Arec:Failed to allocate filename!");
    594           return -ENOMEM;
    595     }
    596     if (optind > argc - 1) {
    597         free(filename);
    598         filename = NULL;
    599     } else {
    600         strlcpy(filename, argv[optind++], 30);
    601     }
    602 
    603     memset(&sa, 0, sizeof(sa));
    604     sa.sa_handler = &signal_handler;
    605     sigaction(SIGABRT, &sa, NULL);
    606 
    607     if (pcm_flag) {
    608 	 if (format == SNDRV_PCM_FORMAT_S16_LE)
    609              rc = rec_wav(mmap, device, rate, ch, filename);
    610          else
    611              rc = rec_raw(mmap, device, rate, ch, filename);
    612     } else {
    613         rc = rec_wav(mmap, device, rate, ch, "dummy");
    614     }
    615     if (filename)
    616         free(filename);
    617 
    618     return rc;
    619 }
    620 
    621