Chinaunix首页 | 论坛 | 博客
  • 博客访问: 829634
  • 博文数量: 125
  • 博客积分: 4066
  • 博客等级: 上校
  • 技术积分: 1401
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-03 18:58
文章分类

全部博文(125)

文章存档

2014年(1)

2013年(1)

2012年(2)

2011年(29)

2010年(92)

我的朋友

分类: LINUX

2010-06-01 19:13:36

/*
 * 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;
}


阅读(10778) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~