C++程序  |  621行  |  18.56 KB

/*
** Copyright 2010, The Android Open-Source Project
** Copyright (c) 2011-2012, Code Aurora Forum. 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;
}