/*
* aplay.c - plays and records
*
* CREATIVE LABS CHANNEL-files
* Microsoft WAVE-files
* SPARC AUDIO .AU-files
* Raw Data
*
* Copyright (c) by Jaroslav Kysela
* Based on vplay program by Michael Beck
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <malloc.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <fcntl.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <time.h>
#include <locale.h>
#include <alsa/asoundlib.h>
#include <assert.h>
#include <sys/poll.h>
#include <sys/uio.h>
#include <sys/time.h>
#include <sys/signal.h>
#include <asm/byteorder.h>
#include "aconfig.h"
#include "gettext.h"
#include "formats.h"
#include "version.h"
#ifndef LLONG_MAX
#define LLONG_MAX 9223372036854775807LL
#endif
#define DEFAULT_FORMAT SND_PCM_FORMAT_U8
#define DEFAULT_SPEED 8000
#define FORMAT_DEFAULT -1
#define FORMAT_WAVE 0
/* global data */
static snd_pcm_sframes_t (*readi_func)(snd_pcm_t *handle, void *buffer, snd_pcm_uframes_t size);
static snd_pcm_sframes_t (*writei_func)(snd_pcm_t *handle, const void *buffer, snd_pcm_uframes_t size);
static snd_pcm_sframes_t (*readn_func)(snd_pcm_t *handle, void **bufs, snd_pcm_uframes_t size);
static snd_pcm_sframes_t (*writen_func)(snd_pcm_t *handle, void **bufs, snd_pcm_uframes_t size);
enum {
VUMETER_NONE,
VUMETER_MONO,
VUMETER_STEREO
};
static char *command;
static snd_pcm_t *handle;
static struct {
snd_pcm_format_t format;
unsigned int channels;
unsigned int rate;
} hwparams, rhwparams;
static int timelimit = 0;
static int quiet_mode = 0;
static int file_type = FORMAT_DEFAULT;
static int open_mode = 0;
static snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK;
static int mmap_flag = 0;
static int interleaved = 1;
static int nonblock = 0;
static u_char *audiobuf = NULL;
static snd_pcm_uframes_t chunk_size = 0;
static unsigned period_time = 0;
static unsigned buffer_time = 0;
static snd_pcm_uframes_t period_frames = 0;
static snd_pcm_uframes_t buffer_frames = 0;
static int avail_min = -1;
static int start_delay = 0;
static int stop_delay = 0;
static int monotonic = 0;
static int verbose = 0;
static int vumeter = VUMETER_NONE;
static int buffer_pos = 0;
static size_t bits_per_sample, bits_per_frame;
static size_t chunk_bytes;
static int test_position = 0;
static int test_coef = 8;
static int test_nowait = 0;
static snd_output_t *log;
static long long max_file_size = 0;
static int max_file_time = 0;
static int use_strftime = 0;
volatile static int recycle_capture_file = 0;
static int fd = -1;
static off64_t pbrec_count = LLONG_MAX, fdcount;
static int vocmajor, vocminor;
static char *pidfile_name = NULL;
FILE *pidf = NULL;
static int pidfile_written = 0;
/* needed prototypes */
static void playback(char *filename);
static void capture(char *filename);
static void playbackv(char **filenames, unsigned int count);
static void capturev(char **filenames, unsigned int count);
static void begin_wave(int fd, size_t count);
static void end_wave(int fd);
static const struct fmt_capture {
void (*start) (int fd, size_t count);
void (*end) (int fd);
char *what;
long long max_filesize;
} fmt_rec_table[] = {
/* FIXME: can WAV handle exactly 2GB or less than it? */
{ begin_wave, end_wave, N_("WAVE"), 2147483648LL }
};
#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95)
#define error(...) do {\
fprintf(stderr, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \
fprintf(stderr, __VA_ARGS__); \
putc('\n', stderr); \
} while (0)
#else
#define error(args...) do {\
fprintf(stderr, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \
fprintf(stderr, ##args); \
putc('\n', stderr); \
} while (0)
#endif
static void usage(char *command)
{
snd_pcm_format_t k;
printf(
_("Usage: %s [OPTION]... [FILE]...\n"
"\n"
"-h, --help help\n"
" --version print current version\n"
"-l, --list-devices list all soundcards and digital audio devices\n"
"-L, --list-pcms list device names\n"
"-D, --device=NAME select PCM by name\n"
"-q, --quiet quiet mode\n"
"-t, --file-type TYPE file type (voc, wav, raw or au)\n"
"-c, --channels=# channels\n"
"-f, --format=FORMAT sample format (case insensitive)\n"
"-r, --rate=# sample rate\n"
"-d, --duration=# interrupt after # seconds\n"
"-M, --mmap mmap stream\n"
"-N, --nonblock nonblocking mode\n"
"-F, --period-time=# distance between interrupts is # microseconds\n"
"-B, --buffer-time=# buffer duration is # microseconds\n"
" --period-size=# distance between interrupts is # frames\n"
" --buffer-size=# buffer duration is # frames\n"
"-A, --avail-min=# min available space for wakeup is # microseconds\n"
"-R, --start-delay=# delay for automatic PCM start is # microseconds \n"
" (relative to buffer size if <= 0)\n"
"-T, --stop-delay=# delay for automatic PCM stop is # microseconds from xrun\n"
"-v, --verbose show PCM structure and setup (accumulative)\n"
"-V, --vumeter=TYPE enable VU meter (TYPE: mono or stereo)\n"
"-I, --separate-channels one file for each channel\n"
" --disable-resample disable automatic rate resample\n"
" --disable-channels disable automatic channel conversions\n"
" --disable-format disable automatic format conversions\n"
" --disable-softvol disable software volume control (softvol)\n"
" --test-position test ring buffer position\n"
" --test-coef=# test coeficient for ring buffer position (default 8)\n"
" expression for validation is: coef * (buffer_size / 2)\n"
" --test-nowait do not wait for ring buffer - eats whole CPU\n"
" --max-file-time=# start another output file when the old file has recorded\n"
" for this many seconds\n"
" --process-id-file write the process ID here\n"
" --use-strftime apply the strftime facility to the output file name\n")
, command);
printf(_("Recognized sample formats are:"));
for (k = 0; k < SND_PCM_FORMAT_LAST; ++k) {
const char *s = snd_pcm_format_name(k);
if (s)
printf(" %s", s);
}
printf(_("\nSome of these may not be available on selected hardware\n"));
printf(_("The availabled format shortcuts are:\n"));
printf(_("-f cd (16 bit little endian, 44100, stereo)\n"));
printf(_("-f cdr (16 bit big endian, 44100, stereo)\n"));
printf(_("-f dat (16 bit little endian, 48000, stereo)\n"));
}
static void device_list(void)
{
snd_ctl_t *handle;
int card, err, dev, idx;
snd_ctl_card_info_t *info;
snd_pcm_info_t *pcminfo;
snd_ctl_card_info_alloca(&info);
snd_pcm_info_alloca(&pcminfo);
card = -1;
if (snd_card_next(&card) < 0 || card < 0) {
error(_("no soundcards found..."));
return;
}
printf(_("**** List of %s Hardware Devices ****\n"),
snd_pcm_stream_name(stream));
while (card >= 0) {
char name[32];
sprintf(name, "hw:%d", card);
if ((err = snd_ctl_open(&handle, name, 0)) < 0) {
error("control open (%i): %s", card, snd_strerror(err));
goto next_card;
}
if ((err = snd_ctl_card_info(handle, info)) < 0) {
error("control hardware info (%i): %s", card, snd_strerror(err));
snd_ctl_close(handle);
goto next_card;
}
dev = -1;
while (1) {
unsigned int count;
if (snd_ctl_pcm_next_device(handle, &dev)<0)
error("snd_ctl_pcm_next_device");
if (dev < 0)
break;
snd_pcm_info_set_device(pcminfo, dev);
snd_pcm_info_set_subdevice(pcminfo, 0);
snd_pcm_info_set_stream(pcminfo, stream);
if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) {
if (err != -ENOENT)
error("control digital audio info (%i): %s", card, snd_strerror(err));
continue;
}
printf(_("card %i: %s [%s], device %i: %s [%s]\n"),
card, snd_ctl_card_info_get_id(info), snd_ctl_card_info_get_name(info),
dev,
snd_pcm_info_get_id(pcminfo),
snd_pcm_info_get_name(pcminfo));
count = snd_pcm_info_get_subdevices_count(pcminfo);
printf( _(" Subdevices: %i/%i\n"),
snd_pcm_info_get_subdevices_avail(pcminfo), count);
for (idx = 0; idx < (int)count; idx++) {
snd_pcm_info_set_subdevice(pcminfo, idx);
if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) {
error("control digital audio playback info (%i): %s", card, snd_strerror(err));
} else {
printf(_(" Subdevice #%i: %s\n"),
idx, snd_pcm_info_get_subdevice_name(pcminfo));
}
}
}
snd_ctl_close(handle);
next_card:
if (snd_card_next(&card) < 0) {
error("snd_card_next");
break;
}
}
}
static void pcm_list(void)
{
void **hints, **n;
char *name, *descr, *descr1, *io;
const char *filter;
if (snd_device_name_hint(-1, "pcm", &hints) < 0)
return;
n = hints;
filter = stream == SND_PCM_STREAM_CAPTURE ? "Input" : "Output";
while (*n != NULL) {
name = snd_device_name_get_hint(*n, "NAME");
descr = snd_device_name_get_hint(*n, "DESC");
io = snd_device_name_get_hint(*n, "IOID");
if (io != NULL && strcmp(io, filter) != 0)
goto __end;
printf("%s\n", name);
if ((descr1 = descr) != NULL) {
printf(" ");
while (*descr1) {
if (*descr1 == '\n')
printf("\n ");
else
putchar(*descr1);
descr1++;
}
putchar('\n');
}
__end:
if (name != NULL)
free(name);
if (descr != NULL)
free(descr);
if (io != NULL)
free(io);
n++;
}
snd_device_name_free_hint(hints);
}
static void version(void)
{
printf("%s: version " SND_UTIL_VERSION_STR " by Jaroslav Kysela \n", command);
}
/*
* Subroutine to clean up before exit.
*/
static void prg_exit(int code)
{
if (handle)
snd_pcm_close(handle);
if (pidfile_written)
remove (pidfile_name);
exit(code);
}
static void signal_handler(int sig)
{
if (verbose==2)
putchar('\n');
if (!quiet_mode)
fprintf(stderr, _("Aborted by signal %s...\n"), strsignal(sig));
if (stream == SND_PCM_STREAM_CAPTURE) {
if (fmt_rec_table[file_type].end) {
fmt_rec_table[file_type].end(fd);
fd = -1;
}
stream = -1;
}
if (fd > 1) {
close(fd);
fd = -1;
}
if (handle && sig != SIGABRT) {
snd_pcm_close(handle);
handle = NULL;
}
prg_exit(EXIT_FAILURE);
}
/* call on SIGUSR1 signal. */
static void signal_handler_recycle (int sig)
{
/* flag the capture loop to start a new output file */
recycle_capture_file = 1;
}
enum {
OPT_VERSION = 1,
OPT_PERIOD_SIZE,
OPT_BUFFER_SIZE,
OPT_DISABLE_RESAMPLE,
OPT_DISABLE_CHANNELS,
OPT_DISABLE_FORMAT,
OPT_DISABLE_SOFTVOL,
OPT_TEST_POSITION,
OPT_TEST_COEF,
OPT_TEST_NOWAIT,
OPT_MAX_FILE_TIME,
OPT_PROCESS_ID_FILE,
OPT_USE_STRFTIME
};
int main(int argc, char *argv[])
{
int option_index;
static const char short_options[] = "hnlLD:qt:c:f:r:d:MNF:A:R:T:B:vV:IPC";
static const struct option long_options[] = {
{"help", 0, 0, 'h'},
{"version", 0, 0, OPT_VERSION},
{"list-devnames", 0, 0, 'n'},
{"list-devices", 0, 0, 'l'},
{"list-pcms", 0, 0, 'L'},
{"device", 1, 0, 'D'},
{"quiet", 0, 0, 'q'},
{"file-type", 1, 0, 't'},
{"channels", 1, 0, 'c'},
{"format", 1, 0, 'f'},
{"rate", 1, 0, 'r'},
{"duration", 1, 0 ,'d'},
{"mmap", 0, 0, 'M'},
{"nonblock", 0, 0, 'N'},
{"period-time", 1, 0, 'F'},
{"period-size", 1, 0, OPT_PERIOD_SIZE},
{"avail-min", 1, 0, 'A'},
{"start-delay", 1, 0, 'R'},
{"stop-delay", 1, 0, 'T'},
{"buffer-time", 1, 0, 'B'},
{"buffer-size", 1, 0, OPT_BUFFER_SIZE},
{"verbose", 0, 0, 'v'},
{"vumeter", 1, 0, 'V'},
{"separate-channels", 0, 0, 'I'},
{"playback", 0, 0, 'P'},
{"capture", 0, 0, 'C'},
{"disable-resample", 0, 0, OPT_DISABLE_RESAMPLE},
{"disable-channels", 0, 0, OPT_DISABLE_CHANNELS},
{"disable-format", 0, 0, OPT_DISABLE_FORMAT},
{"disable-softvol", 0, 0, OPT_DISABLE_SOFTVOL},
{"test-position", 0, 0, OPT_TEST_POSITION},
{"test-coef", 1, 0, OPT_TEST_COEF},
{"test-nowait", 0, 0, OPT_TEST_NOWAIT},
{"max-file-time", 1, 0, OPT_MAX_FILE_TIME},
{"process-id-file", 1, 0, OPT_PROCESS_ID_FILE},
{"use-strftime", 0, 0, OPT_USE_STRFTIME},
{0, 0, 0, 0}
};
char *pcm_name = "default";
int tmp, err, c;
int do_device_list = 0, do_pcm_list = 0;
snd_pcm_info_t *info;
#ifdef ENABLE_NLS
setlocale(LC_ALL, "");
textdomain(PACKAGE);
#endif
snd_pcm_info_alloca(&info);
err = snd_output_stdio_attach(&log, stderr, 0);
assert(err >= 0);
command = argv[0];
file_type = FORMAT_DEFAULT;
if (strstr(argv[0], "arecord")) {
stream = SND_PCM_STREAM_CAPTURE;
file_type = FORMAT_WAVE;
command = "arecord";
start_delay = 1;
} else if (strstr(argv[0], "aplay")) {
stream = SND_PCM_STREAM_PLAYBACK;
command = "aplay";
} else {
error(_("command should be named either arecord or aplay"));
return 1;
}
chunk_size = -1;
rhwparams.format = DEFAULT_FORMAT;
rhwparams.rate = DEFAULT_SPEED;
rhwparams.channels = 1;
while ((c = getopt_long(argc, argv, short_options, long_options, &option_index)) != -1) {
switch (c) {
case 'h':
usage(command);
return 0;
case OPT_VERSION:
version();
return 0;
case 'l':
do_device_list = 1;
break;
case 'L':
do_pcm_list = 1;
break;
case 'D':
pcm_name = optarg;
break;
case 'q':
quiet_mode = 1;
break;
case 't':
if (strcasecmp(optarg, "wav") == 0)
file_type = FORMAT_WAVE;
else {
error(_("unrecognized file format %s"), optarg);
return 1;
}
break;
case 'c':
rhwparams.channels = strtol(optarg, NULL, 0);
if (rhwparams.channels < 1 || rhwparams.channels > 32) {
error(_("value %i for channels is invalid"), rhwparams.channels);
return 1;
}
break;
case 'f':
if (strcasecmp(optarg, "cd") == 0 || strcasecmp(optarg, "cdr") == 0) {
if (strcasecmp(optarg, "cdr") == 0)
rhwparams.format = SND_PCM_FORMAT_S16_BE;
else
rhwparams.format = SND_PCM_FORMAT_S16_LE;
rhwparams.rate = 44100;
rhwparams.channels = 2;
} else if (strcasecmp(optarg, "dat") == 0) {
rhwparams.format = SND_PCM_FORMAT_S16_LE;
rhwparams.rate = 48000;
rhwparams.channels = 2;
} else {
rhwparams.format = snd_pcm_format_value(optarg);
if (rhwparams.format == SND_PCM_FORMAT_UNKNOWN) {
error(_("wrong extended format '%s'"), optarg);
prg_exit(EXIT_FAILURE);
}
}
break;
case 'r':
tmp = strtol(optarg, NULL, 0);
if (tmp < 300)
tmp *= 1000;
rhwparams.rate = tmp;
if (tmp < 2000 || tmp > 192000) {
error(_("bad speed value %i"), tmp);
return 1;
}
break;
case 'd':
timelimit = strtol(optarg, NULL, 0);
break;
case 'N':
nonblock = 1;
open_mode |= SND_PCM_NONBLOCK;
break;
case 'F':
period_time = strtol(optarg, NULL, 0);
break;
case 'B':
buffer_time = strtol(optarg, NULL, 0);
break;
case OPT_PERIOD_SIZE:
period_frames = strtol(optarg, NULL, 0);
break;
case OPT_BUFFER_SIZE:
buffer_frames = strtol(optarg, NULL, 0);
break;
case 'A':
avail_min = strtol(optarg, NULL, 0);
break;
case 'R':
start_delay = strtol(optarg, NULL, 0);
break;
case 'T':
stop_delay = strtol(optarg, NULL, 0);
break;
case 'v':
verbose++;
if (verbose > 1 && !vumeter)
vumeter = VUMETER_MONO;
break;
case 'V':
if (*optarg == 's')
vumeter = VUMETER_STEREO;
else if (*optarg == 'm')
vumeter = VUMETER_MONO;
else
vumeter = VUMETER_NONE;
break;
case 'M':
mmap_flag = 1;
break;
case 'I':
interleaved = 0;
break;
case 'P':
stream = SND_PCM_STREAM_PLAYBACK;
command = "aplay";
break;
case 'C':
stream = SND_PCM_STREAM_CAPTURE;
command = "arecord";
start_delay = 1;
if (file_type == FORMAT_DEFAULT)
file_type = FORMAT_WAVE;
break;
case OPT_DISABLE_RESAMPLE:
open_mode |= SND_PCM_NO_AUTO_RESAMPLE;
break;
case OPT_DISABLE_CHANNELS:
open_mode |= SND_PCM_NO_AUTO_CHANNELS;
break;
case OPT_DISABLE_FORMAT:
open_mode |= SND_PCM_NO_AUTO_FORMAT;
break;
case OPT_DISABLE_SOFTVOL:
open_mode |= SND_PCM_NO_SOFTVOL;
break;
case OPT_TEST_POSITION:
test_position = 1;
break;
case OPT_TEST_COEF:
test_coef = strtol(optarg, NULL, 0);
if (test_coef < 1)
test_coef = 1;
break;
case OPT_TEST_NOWAIT:
test_nowait = 1;
break;
case OPT_MAX_FILE_TIME:
max_file_time = strtol(optarg, NULL, 0);
break;
case OPT_PROCESS_ID_FILE:
pidfile_name = optarg;
break;
case OPT_USE_STRFTIME:
use_strftime = 1;
break;
default:
fprintf(stderr, _("Try `%s --help' for more information.\n"), command);
return 1;
}
}
if (do_device_list) {
if (do_pcm_list) pcm_list();
device_list();
goto __end;
} else if (do_pcm_list) {
pcm_list();
goto __end;
}
err = snd_pcm_open(&handle, pcm_name, stream, open_mode);
if (err < 0) {
error(_("audio open error: %s"), snd_strerror(err));
return 1;
}
if ((err = snd_pcm_info(handle, info)) < 0) {
error(_("info error: %s"), snd_strerror(err));
return 1;
}
if (nonblock) {
err = snd_pcm_nonblock(handle, 1);
if (err < 0) {
error(_("nonblock setting error: %s"), snd_strerror(err));
return 1;
}
}
chunk_size = 1024;
hwparams = rhwparams;
audiobuf = (u_char *)malloc(1024);
if (audiobuf == NULL) {
error(_("not enough memory"));
return 1;
}
if (mmap_flag) {
writei_func = snd_pcm_mmap_writei;
readi_func = snd_pcm_mmap_readi;
writen_func = snd_pcm_mmap_writen;
readn_func = snd_pcm_mmap_readn;
} else {
writei_func = snd_pcm_writei;
readi_func = snd_pcm_readi;
writen_func = snd_pcm_writen;
readn_func = snd_pcm_readn;
}
if (pidfile_name) {
errno = 0;
pidf = fopen (pidfile_name, "w");
if (pidf) {
(void)fprintf (pidf, "%d\n", getpid());
fclose(pidf);
pidfile_written = 1;
} else {
error(_("Cannot create process ID file %s: %s"),
pidfile_name, strerror (errno));
return 1;
}
}
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
signal(SIGABRT, signal_handler);
signal(SIGUSR1, signal_handler_recycle);
if (interleaved) {
if (optind > argc - 1) {
if (stream == SND_PCM_STREAM_PLAYBACK)
playback(NULL);
else
capture(NULL);
} else {
while (optind <= argc - 1) {
if (stream == SND_PCM_STREAM_PLAYBACK)
playback(argv[optind++]);
else
capture(argv[optind++]);
}
}
}
if (verbose==2)
putchar('\n');
snd_pcm_close(handle);
handle = NULL;
free(audiobuf);
__end:
snd_output_close(log);
snd_config_update_free_global();
prg_exit(EXIT_SUCCESS);
/* avoid warning */
return EXIT_SUCCESS;
}
|