blob: 30e710c36ac94d2cb39dc2996a81141778076b61 [file] [log] [blame]
/*
** 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;
}