| /* |
| ** Copyright 2010, The Android Open-Source Project |
| ** Copyright (c) 2011-2012, The Linux Foundation. All rights reserved. |
| ** |
| ** Licensed under the Apache License, Version 2.0 (the "License"); |
| ** you may not use this file except in compliance with the License. |
| ** You may obtain a copy of the License at |
| ** |
| ** http://www.apache.org/licenses/LICENSE-2.0 |
| ** |
| ** Unless required by applicable law or agreed to in writing, software |
| ** distributed under the License is distributed on an "AS IS" BASIS, |
| ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| ** See the License for the specific language governing permissions and |
| ** limitations under the License. |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <stdint.h> |
| #include <string.h> |
| #include <signal.h> |
| #include <errno.h> |
| #include <sys/poll.h> |
| #include <sys/ioctl.h> |
| #include <getopt.h> |
| #include <limits.h> |
| |
| #include "alsa_audio.h" |
| |
| #define ID_RIFF 0x46464952 |
| #define ID_WAVE 0x45564157 |
| #define ID_FMT 0x20746d66 |
| #define ID_DATA 0x61746164 |
| |
| #define FORMAT_PCM 1 |
| |
| #ifndef ANDROID |
| #define strlcat g_strlcat |
| #define strlcpy g_strlcpy |
| #endif |
| |
| static struct wav_header hdr; |
| static int fd; |
| static struct pcm *pcm; |
| static int debug = 0; |
| static int pcm_flag = 1; |
| static int duration = 0; |
| static char *filename; |
| static char *data; |
| static int format = SNDRV_PCM_FORMAT_S16_LE; |
| static int period = 0; |
| static int piped = 0; |
| |
| static struct option long_options[] = |
| { |
| {"pcm", 0, 0, 'P'}, |
| {"debug", 0, 0, 'V'}, |
| {"Mmap", 0, 0, 'M'}, |
| {"HW", 1, 0, 'D'}, |
| {"Rate", 1, 0, 'R'}, |
| {"channel", 1, 0, 'C'}, |
| {"duration", 1, 0, 'T'}, |
| {"format", 1, 0, 'F'}, |
| {"period", 1, 0, 'B'}, |
| {0, 0, 0, 0} |
| }; |
| |
| struct wav_header { |
| uint32_t riff_id; |
| uint32_t riff_sz; |
| uint32_t riff_fmt; |
| uint32_t fmt_id; |
| uint32_t fmt_sz; |
| uint16_t audio_format; |
| uint16_t num_channels; |
| uint32_t sample_rate; |
| uint32_t byte_rate; /* sample_rate * num_channels * bps / 8 */ |
| uint16_t block_align; /* num_channels * bps / 8 */ |
| uint16_t bits_per_sample; |
| uint32_t data_id; |
| uint32_t data_sz; |
| }; |
| |
| static int set_params(struct pcm *pcm) |
| { |
| struct snd_pcm_hw_params *params; |
| struct snd_pcm_sw_params *sparams; |
| |
| unsigned long periodSize, bufferSize, reqBuffSize; |
| unsigned int periodTime, bufferTime; |
| unsigned int requestedRate = pcm->rate; |
| |
| params = (struct snd_pcm_hw_params*) calloc(1, sizeof(struct snd_pcm_hw_params)); |
| if (!params) { |
| fprintf(stderr, "Arec:Failed to allocate ALSA hardware parameters!"); |
| return -ENOMEM; |
| } |
| |
| param_init(params); |
| |
| param_set_mask(params, SNDRV_PCM_HW_PARAM_ACCESS, |
| (pcm->flags & PCM_MMAP)? SNDRV_PCM_ACCESS_MMAP_INTERLEAVED : SNDRV_PCM_ACCESS_RW_INTERLEAVED); |
| param_set_mask(params, SNDRV_PCM_HW_PARAM_FORMAT, pcm->format); |
| param_set_mask(params, SNDRV_PCM_HW_PARAM_SUBFORMAT, |
| SNDRV_PCM_SUBFORMAT_STD); |
| if (period) |
| param_set_min(params, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, period); |
| else |
| param_set_min(params, SNDRV_PCM_HW_PARAM_PERIOD_TIME, 10); |
| param_set_int(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, 16); |
| param_set_int(params, SNDRV_PCM_HW_PARAM_FRAME_BITS, |
| pcm->channels * 16); |
| param_set_int(params, SNDRV_PCM_HW_PARAM_CHANNELS, |
| pcm->channels); |
| param_set_int(params, SNDRV_PCM_HW_PARAM_RATE, pcm->rate); |
| |
| param_set_hw_refine(pcm, params); |
| |
| if (param_set_hw_params(pcm, params)) { |
| fprintf(stderr, "Arec:cannot set hw params"); |
| return -errno; |
| } |
| if (debug) |
| param_dump(params); |
| |
| pcm->buffer_size = pcm_buffer_size(params); |
| pcm->period_size = pcm_period_size(params); |
| pcm->period_cnt = pcm->buffer_size/pcm->period_size; |
| if (debug) { |
| fprintf (stderr,"period_size (%d)", pcm->period_size); |
| fprintf (stderr," buffer_size (%d)", pcm->buffer_size); |
| fprintf (stderr," period_cnt (%d)\n", pcm->period_cnt); |
| } |
| sparams = (struct snd_pcm_sw_params*) calloc(1, sizeof(struct snd_pcm_sw_params)); |
| if (!sparams) { |
| fprintf(stderr, "Arec:Failed to allocate ALSA software parameters!\n"); |
| return -ENOMEM; |
| } |
| sparams->tstamp_mode = SNDRV_PCM_TSTAMP_NONE; |
| sparams->period_step = 1; |
| |
| if (pcm->flags & PCM_MONO) { |
| sparams->avail_min = pcm->period_size/2; |
| sparams->xfer_align = pcm->period_size/2; |
| } else if (pcm->flags & PCM_QUAD) { |
| sparams->avail_min = pcm->period_size/8; |
| sparams->xfer_align = pcm->period_size/8; |
| } else if (pcm->flags & PCM_5POINT1) { |
| sparams->avail_min = pcm->period_size/12; |
| sparams->xfer_align = pcm->period_size/12; |
| } else { |
| sparams->avail_min = pcm->period_size/4; |
| sparams->xfer_align = pcm->period_size/4; |
| } |
| |
| sparams->start_threshold = 1; |
| sparams->stop_threshold = INT_MAX; |
| sparams->silence_size = 0; |
| sparams->silence_threshold = 0; |
| |
| if (param_set_sw_params(pcm, sparams)) { |
| fprintf(stderr, "Arec:cannot set sw params"); |
| return -errno; |
| } |
| if (debug) { |
| fprintf (stderr,"avail_min (%lu)\n", sparams->avail_min); |
| fprintf (stderr,"start_threshold (%lu)\n", sparams->start_threshold); |
| fprintf (stderr,"stop_threshold (%lu)\n", sparams->stop_threshold); |
| fprintf (stderr,"xfer_align (%lu)\n", sparams->xfer_align); |
| } |
| return 0; |
| |
| } |
| |
| int record_file(unsigned rate, unsigned channels, int fd, unsigned count, unsigned flags, const char *device) |
| { |
| unsigned xfer, bufsize; |
| int r, avail; |
| int nfds = 1; |
| static int start = 0; |
| struct snd_xferi x; |
| long frames; |
| unsigned offset = 0; |
| int err; |
| struct pollfd pfd[1]; |
| int rec_size = 0; |
| |
| flags |= PCM_IN; |
| |
| if (channels == 1) |
| flags |= PCM_MONO; |
| else if (channels == 4) |
| flags |= PCM_QUAD; |
| else if (channels == 6) |
| flags |= PCM_5POINT1; |
| else |
| flags |= PCM_STEREO; |
| |
| pcm = pcm_open(flags, device); |
| if (!pcm_ready(pcm)) { |
| pcm_close(pcm); |
| goto fail; |
| } |
| pcm->channels = channels; |
| pcm->rate = rate; |
| pcm->flags = flags; |
| pcm->format = format; |
| if (set_params(pcm)) { |
| fprintf(stderr, "Arec:params setting failed\n"); |
| pcm_close(pcm); |
| return -EINVAL; |
| } |
| |
| if (!pcm_flag) { |
| if (pcm_prepare(pcm)) { |
| fprintf(stderr, "Arec:Failed in pcm_prepare\n"); |
| pcm_close(pcm); |
| return -errno; |
| } |
| if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_START)) { |
| fprintf(stderr, "Arec: Hostless IOCTL_START Error no %d \n", errno); |
| pcm_close(pcm); |
| return -errno; |
| } |
| while(1); |
| } |
| |
| if (flags & PCM_MMAP) { |
| u_int8_t *dst_addr = NULL; |
| struct snd_pcm_sync_ptr *sync_ptr1 = pcm->sync_ptr; |
| unsigned int tmp; |
| |
| if (mmap_buffer(pcm)) { |
| fprintf(stderr, "Arec:params setting failed\n"); |
| pcm_close(pcm); |
| return -EINVAL; |
| } |
| if (debug) |
| fprintf(stderr, "Arec:mmap_buffer done\n"); |
| |
| if (pcm_prepare(pcm)) { |
| fprintf(stderr, "Arec:Failed in pcm_prepare\n"); |
| pcm_close(pcm); |
| return -errno; |
| } |
| |
| bufsize = pcm->period_size; |
| if (debug) |
| fprintf(stderr, "Arec:bufsize = %d\n", bufsize); |
| if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_START)) { |
| if (errno == EPIPE) { |
| fprintf(stderr, "Arec:Failed in SNDRV_PCM_IOCTL_START\n"); |
| /* we failed to make our window -- try to restart */ |
| pcm->running = 0; |
| } else { |
| fprintf(stderr, "Arec:Error no %d \n", errno); |
| return -errno; |
| } |
| } |
| |
| pfd[0].fd = pcm->fd; |
| pfd[0].events = POLLIN; |
| |
| hdr.data_sz = 0; |
| if (pcm->flags & PCM_MONO) { |
| frames = bufsize / 2; |
| } else if (pcm->flags & PCM_QUAD) { |
| frames = bufsize / 8; |
| } else if (pcm->flags & PCM_5POINT1) { |
| frames = bufsize / 12; |
| } else{ |
| frames = bufsize / 4; |
| } |
| x.frames = frames; |
| for(;;) { |
| if (!pcm->running) { |
| if (pcm_prepare(pcm)) |
| return --errno; |
| start = 0; |
| } |
| /* Sync the current Application pointer from the kernel */ |
| pcm->sync_ptr->flags = SNDRV_PCM_SYNC_PTR_APPL | SNDRV_PCM_SYNC_PTR_AVAIL_MIN;//SNDRV_PCM_SYNC_PTR_HWSYNC; |
| err = sync_ptr(pcm); |
| if (err == EPIPE) { |
| fprintf(stderr, "Arec:Failed in sync_ptr \n"); |
| /* we failed to make our window -- try to restart */ |
| //pcm->overruns++; |
| pcm->running = 0; |
| continue; |
| } |
| /* |
| * Check for the available data in driver. If available data is |
| * less than avail_min we need to wait |
| */ |
| avail = pcm_avail(pcm); |
| if (debug) |
| fprintf(stderr, "Arec:avail 1 = %d frames = %ld\n",avail, frames); |
| if (avail < 0) |
| return avail; |
| if (avail < pcm->sw_p->avail_min) { |
| poll(pfd, nfds, TIMEOUT_INFINITE); |
| continue; |
| } |
| if (x.frames > avail) |
| frames = avail; |
| /* |
| * Now that we have data size greater than avail_min available to |
| * to be read we need to calcutate the buffer offset where we can |
| * start reading from. |
| */ |
| dst_addr = dst_address(pcm); |
| |
| /* |
| * Write to the file at the destination address from kernel mmaped buffer |
| * This reduces a extra copy of intermediate buffer. |
| */ |
| if (write(fd, dst_addr, bufsize) != bufsize) { |
| fprintf(stderr, "Arec:could not write %d bytes\n", bufsize); |
| return -errno; |
| } |
| x.frames -= frames; |
| pcm->sync_ptr->c.control.appl_ptr += frames; |
| pcm->sync_ptr->flags = 0; |
| err = sync_ptr(pcm); |
| if (err == EPIPE) { |
| fprintf(stderr, "Arec:Failed in sync_ptr \n"); |
| /* we failed to make our window -- try to restart */ |
| pcm->running = 0; |
| continue; |
| } |
| rec_size += bufsize; |
| hdr.data_sz += bufsize; |
| hdr.riff_sz = hdr.data_sz + 44 - 8; |
| if (!piped) { |
| lseek(fd, 0, SEEK_SET); |
| write(fd, &hdr, sizeof(hdr)); |
| lseek(fd, 0, SEEK_END); |
| } |
| if (rec_size >= count) |
| break; |
| } |
| } else { |
| bufsize = pcm->period_size; |
| if (pcm_prepare(pcm)) { |
| fprintf(stderr, "Arec:Failed in pcm_prepare\n"); |
| pcm_close(pcm); |
| return -errno; |
| } |
| |
| data = calloc(1, bufsize); |
| if (!data) { |
| fprintf(stderr, "Arec:could not allocate %d bytes\n", bufsize); |
| return -ENOMEM; |
| } |
| |
| while (!pcm_read(pcm, data, bufsize)) { |
| if (write(fd, data, bufsize) != bufsize) { |
| fprintf(stderr, "Arec:could not write %d bytes\n", bufsize); |
| break; |
| } |
| rec_size += bufsize; |
| hdr.data_sz += bufsize; |
| hdr.riff_sz = hdr.data_sz + 44 - 8; |
| if (!piped) { |
| lseek(fd, 0, SEEK_SET); |
| write(fd, &hdr, sizeof(hdr)); |
| lseek(fd, 0, SEEK_END); |
| } |
| if (rec_size >= count) |
| break; |
| } |
| } |
| fprintf(stderr, " rec_size =%d count =%d\n", rec_size, count); |
| close(fd); |
| free(data); |
| pcm_close(pcm); |
| return hdr.data_sz; |
| |
| fail: |
| fprintf(stderr, "Arec:pcm error: %s\n", pcm_error(pcm)); |
| return -errno; |
| } |
| |
| int rec_raw(const char *fg, const char *device, int rate, int ch, |
| const char *fn) |
| { |
| unsigned flag = 0; |
| uint32_t rec_max_sz = 2147483648LL; |
| uint32_t count; |
| int i = 0; |
| |
| if (!fn) { |
| fd = fileno(stdout); |
| piped = 1; |
| } else { |
| fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0664); |
| if (fd < 0) { |
| fprintf(stderr, "Arec:arec: cannot open '%s'\n", fn); |
| return -EBADFD; |
| } |
| } |
| if (duration == 0) { |
| count = rec_max_sz; |
| } else { |
| count = rate * ch * 2; |
| count *= (uint32_t)duration; |
| } |
| count = count < rec_max_sz ? count : rec_max_sz; |
| if (debug) |
| fprintf(stderr, "arec: %d ch, %d hz, %d bit, format %x\n", |
| ch, rate, 16, format); |
| |
| if (!strncmp(fg, "M", sizeof("M"))) { |
| flag = PCM_MMAP; |
| } else if (!strncmp(fg, "N", sizeof("N"))) { |
| flag = PCM_NMMAP; |
| } |
| return record_file(rate, ch, fd, count, flag, device); |
| } |
| |
| int rec_wav(const char *fg, const char *device, int rate, int ch, const char *fn) |
| { |
| unsigned flag = 0; |
| uint32_t rec_max_sz = 2147483648LL; |
| uint32_t count = 0; |
| int i = 0; |
| |
| if (pcm_flag) { |
| if (!fn) { |
| fd = fileno(stdout); |
| piped = 1; |
| } else { |
| fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0664); |
| if (fd < 0) { |
| fprintf(stderr, "Arec:arec: cannot open '%s'\n", fn); |
| return -EBADFD; |
| } |
| } |
| memset(&hdr, 0, sizeof(struct wav_header)); |
| hdr.riff_id = ID_RIFF; |
| hdr.riff_fmt = ID_WAVE; |
| hdr.fmt_id = ID_FMT; |
| hdr.fmt_sz = 16; |
| hdr.audio_format = FORMAT_PCM; |
| hdr.num_channels = ch; |
| hdr.sample_rate = rate; |
| hdr.bits_per_sample = 16; |
| hdr.byte_rate = (rate * ch * hdr.bits_per_sample) / 8; |
| hdr.block_align = ( hdr.bits_per_sample * ch ) / 8; |
| hdr.data_id = ID_DATA; |
| hdr.data_sz = 0; |
| |
| if (duration == 0) { |
| count = rec_max_sz; |
| } else { |
| count = rate * ch * 2; |
| count *= (uint32_t)duration; |
| } |
| hdr.riff_sz = hdr.data_sz + 44 - 8; |
| if (write(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) { |
| if (debug) |
| fprintf(stderr, "arec: cannot write header\n"); |
| return -errno; |
| } |
| if (debug) |
| fprintf(stderr, "arec: %d ch, %d hz, %d bit, %s\n", |
| hdr.num_channels, hdr.sample_rate, hdr.bits_per_sample, |
| hdr.audio_format == FORMAT_PCM ? "PCM" : "unknown"); |
| } else { |
| hdr.sample_rate = rate; |
| hdr.num_channels = ch; |
| } |
| |
| if (!strncmp(fg, "M", sizeof("M"))) { |
| flag = PCM_MMAP; |
| } else if (!strncmp(fg, "N", sizeof("N"))) { |
| flag = PCM_NMMAP; |
| } |
| return record_file(hdr.sample_rate, hdr.num_channels, fd, count, flag, device); |
| } |
| |
| static void signal_handler(int sig) |
| { |
| long file_size; |
| FILE *fp; |
| |
| fprintf(stderr, "Arec:Aborted by signal %s...\n", strsignal(sig)); |
| fprintf(stderr, "Arec:lseeked to %d", (int) lseek(fd, 0, SEEK_SET)); |
| hdr.riff_sz = hdr.data_sz + 44 - 8; |
| fprintf(stderr, "Arec: hdr.data_sz =%d\n", hdr.data_sz); |
| fprintf(stderr, "Arec: hdr.riff_sz =%d\n", hdr.riff_sz); |
| if (write(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) { |
| if (debug) |
| fprintf(stderr, "Arec:arec: cannot write header\n"); |
| } else |
| fd = -1; |
| |
| if (fd > 1) { |
| close(fd); |
| fd = -1; |
| } |
| free(filename); |
| free(data); |
| pcm = NULL; |
| raise(sig); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| int rate = 48000; |
| int ch = 1; |
| int i = 0; |
| int option_index = 0; |
| int c; |
| char *mmap = "N"; |
| char *device = "hw:0,0"; |
| struct sigaction sa; |
| int rc = 0; |
| |
| if (argc < 2) { |
| printf("\nUsage: arec [options] <file>\n" |
| "options:\n" |
| "-D <hw:C,D> -- Alsa PCM by name\n" |
| "-M -- Mmap stream\n" |
| "-P -- Hostless steam[No PCM]\n" |
| "-V -- verbose\n" |
| "-C -- Channels\n" |
| "-R -- Rate\n" |
| "-T -- Time in seconds for recording\n" |
| "-F -- Format\n" |
| "-B -- Period\n" |
| "<file> \n"); |
| for (i = 0; i < SNDRV_PCM_FORMAT_LAST; ++i) |
| if (get_format_name(i)) |
| fprintf(stderr, "%s ", get_format_name(i)); |
| fprintf(stderr, "\nSome of these may not be available on selected hardware\n"); |
| return 0; |
| } |
| while ((c = getopt_long(argc, argv, "PVMD:R:C:T:F:B:", long_options, &option_index)) != -1) { |
| switch (c) { |
| case 'P': |
| pcm_flag = 0; |
| break; |
| case 'V': |
| debug = 1; |
| break; |
| case 'M': |
| mmap = "M"; |
| break; |
| case 'D': |
| device = optarg; |
| break; |
| case 'R': |
| rate = (int)strtol(optarg, NULL, 0); |
| break; |
| case 'C': |
| ch = (int)strtol(optarg, NULL, 0); |
| break; |
| case 'T': |
| duration = (int)strtol(optarg, NULL, 0); |
| break; |
| case 'F': |
| format = (int)get_format(optarg); |
| break; |
| case 'B': |
| period = (int)strtol(optarg, NULL, 0); |
| break; |
| default: |
| printf("\nUsage: arec [options] <file>\n" |
| "options:\n" |
| "-D <hw:C,D> -- Alsa PCM by name\n" |
| "-M -- Mmap stream\n" |
| "-P -- Hostless steam[No PCM]\n" |
| "-V -- verbose\n" |
| "-C -- Channels\n" |
| "-R -- Rate\n" |
| "-T -- Time in seconds for recording\n" |
| "-F -- Format\n" |
| "-B -- Period\n" |
| "<file> \n"); |
| for (i = 0; i < SNDRV_PCM_FORMAT_LAST; ++i) |
| if (get_format_name(i)) |
| fprintf(stderr, "%s ", get_format_name(i)); |
| fprintf(stderr, "\nSome of these may not be available on selected hardware\n"); |
| return -EINVAL; |
| } |
| } |
| filename = (char*) calloc(1, 30); |
| if (!filename) { |
| fprintf(stderr, "Arec:Failed to allocate filename!"); |
| return -ENOMEM; |
| } |
| if (optind > argc - 1) { |
| free(filename); |
| filename = NULL; |
| } else { |
| strlcpy(filename, argv[optind++], 30); |
| } |
| |
| memset(&sa, 0, sizeof(sa)); |
| sa.sa_handler = &signal_handler; |
| sigaction(SIGABRT, &sa, NULL); |
| |
| if (pcm_flag) { |
| if (format == SNDRV_PCM_FORMAT_S16_LE) |
| rc = rec_wav(mmap, device, rate, ch, filename); |
| else |
| rc = rec_raw(mmap, device, rate, ch, filename); |
| } else { |
| rc = rec_wav(mmap, device, rate, ch, "dummy"); |
| } |
| if (filename) |
| free(filename); |
| |
| return rc; |
| } |
| |