/* Copyright (C) 2008 The Android Open Source Project */ #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <stdint.h> #include <string.h> #include <sys/mman.h> #include <sys/ioctl.h> #include <unistd.h> #include <linux/ioctl.h> #define AUDIO_IOCTL_MAGIC 'a' #define AUDIO_START _IOW(AUDIO_IOCTL_MAGIC, 0, unsigned) #define AUDIO_STOP _IOW(AUDIO_IOCTL_MAGIC, 1, unsigned) #define AUDIO_FLUSH _IOW(AUDIO_IOCTL_MAGIC, 2, unsigned) #define AUDIO_GET_CONFIG _IOR(AUDIO_IOCTL_MAGIC, 3, unsigned) #define AUDIO_SET_CONFIG _IOW(AUDIO_IOCTL_MAGIC, 4, unsigned) #define AUDIO_GET_STATS _IOR(AUDIO_IOCTL_MAGIC, 5, unsigned) struct msm_audio_config { uint32_t buffer_size; uint32_t buffer_count; uint32_t channel_count; uint32_t sample_rate; uint32_t codec_type; uint32_t unused[3]; }; struct msm_audio_stats { uint32_t out_bytes; uint32_t unused[3]; }; int pcm_play(unsigned rate, unsigned channels, int (*fill)(void *buf, unsigned sz, void *cookie), void *cookie) { struct msm_audio_config config; struct msm_audio_stats stats; unsigned sz, n; char buf[8192]; int afd; afd = open("/dev/msm_pcm_out", O_RDWR); if (afd < 0) { perror("pcm_play: cannot open audio device"); return -1; } if(ioctl(afd, AUDIO_GET_CONFIG, &config)) { perror("could not get config"); return -1; } config.channel_count = channels; config.sample_rate = rate; if (ioctl(afd, AUDIO_SET_CONFIG, &config)) { perror("could not set config"); return -1; } sz = config.buffer_size; if (sz > sizeof(buf)) { fprintf(stderr,"too big\n"); return -1; } fprintf(stderr,"prefill\n"); for (n = 0; n < config.buffer_count; n++) { if (fill(buf, sz, cookie)) break; if (write(afd, buf, sz) != sz) break; } fprintf(stderr,"start\n"); ioctl(afd, AUDIO_START, 0); for (;;) { #if 0 if (ioctl(afd, AUDIO_GET_STATS, &stats) == 0) fprintf(stderr,"%10d\n", stats.out_bytes); #endif if (fill(buf, sz, cookie)) break; if (write(afd, buf, sz) != sz) break; } done: close(afd); return 0; } /* http://ccrma.stanford.edu/courses/422/projects/WaveFormat/ */ #define ID_RIFF 0x46464952 #define ID_WAVE 0x45564157 #define ID_FMT 0x20746d66 #define ID_DATA 0x61746164 #define FORMAT_PCM 1 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 char *next; static unsigned avail; int fill_buffer(void *buf, unsigned sz, void *cookie) { if (sz > avail) return -1; memcpy(buf, next, sz); next += sz; avail -= sz; return 0; } void play_file(unsigned rate, unsigned channels, int fd, unsigned count) { next = malloc(count); if (!next) { fprintf(stderr,"could not allocate %d bytes\n", count); return; } if (read(fd, next, count) != count) { fprintf(stderr,"could not read %d bytes\n", count); return; } avail = count; pcm_play(rate, channels, fill_buffer, 0); } int wav_play(const char *fn) { struct wav_header hdr; unsigned rate, channels; int fd; fd = open(fn, O_RDONLY); if (fd < 0) { fprintf(stderr, "playwav: cannot open '%s'\n", fn); return -1; } if (read(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) { fprintf(stderr, "playwav: cannot read header\n"); return -1; } fprintf(stderr,"playwav: %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"); if ((hdr.riff_id != ID_RIFF) || (hdr.riff_fmt != ID_WAVE) || (hdr.fmt_id != ID_FMT)) { fprintf(stderr, "playwav: '%s' is not a riff/wave file\n", fn); return -1; } if ((hdr.audio_format != FORMAT_PCM) || (hdr.fmt_sz != 16)) { fprintf(stderr, "playwav: '%s' is not pcm format\n", fn); return -1; } if (hdr.bits_per_sample != 16) { fprintf(stderr, "playwav: '%s' is not 16bit per sample\n", fn); return -1; } play_file(hdr.sample_rate, hdr.num_channels, fd, hdr.data_sz); return 0; } int wav_rec(const char *fn, unsigned channels, unsigned rate) { struct wav_header hdr; unsigned char buf[8192]; struct msm_audio_config cfg; unsigned sz, n; int fd, afd; unsigned total = 0; unsigned char tmp; hdr.riff_id = ID_RIFF; hdr.riff_sz = 0; hdr.riff_fmt = ID_WAVE; hdr.fmt_id = ID_FMT; hdr.fmt_sz = 16; hdr.audio_format = FORMAT_PCM; hdr.num_channels = channels; hdr.sample_rate = rate; hdr.byte_rate = hdr.sample_rate * hdr.num_channels * 2; hdr.block_align = hdr.num_channels * 2; hdr.bits_per_sample = 16; hdr.data_id = ID_DATA; hdr.data_sz = 0; fd = open(fn, O_CREAT | O_RDWR, 0666); if (fd < 0) { perror("cannot open output file"); return -1; } write(fd, &hdr, sizeof(hdr)); afd = open("/dev/msm_pcm_in", O_RDWR); if (afd < 0) { perror("cannot open msm_pcm_in"); close(fd); return -1; } /* config change should be a read-modify-write operation */ if (ioctl(afd, AUDIO_GET_CONFIG, &cfg)) { perror("cannot read audio config"); goto fail; } cfg.channel_count = hdr.num_channels; cfg.sample_rate = hdr.sample_rate; if (ioctl(afd, AUDIO_SET_CONFIG, &cfg)) { perror("cannot write audio config"); goto fail; } if (ioctl(afd, AUDIO_GET_CONFIG, &cfg)) { perror("cannot read audio config"); goto fail; } sz = cfg.buffer_size; fprintf(stderr,"buffer size %d x %d\n", sz, cfg.buffer_count); if (sz > sizeof(buf)) { fprintf(stderr,"buffer size %d too large\n", sz); goto fail; } if (ioctl(afd, AUDIO_START, 0)) { perror("cannot start audio"); goto fail; } fcntl(0, F_SETFL, O_NONBLOCK); fprintf(stderr,"\n*** RECORDING * HIT ENTER TO STOP ***\n"); for (;;) { while (read(0, &tmp, 1) == 1) { if ((tmp == 13) || (tmp == 10)) goto done; } if (read(afd, buf, sz) != sz) { perror("cannot read buffer"); goto fail; } if (write(fd, buf, sz) != sz) { perror("cannot write buffer"); goto fail; } total += sz; } done: close(afd); /* update lengths in header */ hdr.data_sz = total; hdr.riff_sz = total + 8 + 16 + 8; lseek(fd, 0, SEEK_SET); write(fd, &hdr, sizeof(hdr)); close(fd); return 0; fail: close(afd); close(fd); unlink(fn); return -1; } int mp3_play(const char *fn) { char buf[64*1024]; int r; int fd, afd; fd = open(fn, O_RDONLY); if (fd < 0) { perror("cannot open mp3 file"); return -1; } afd = open("/dev/msm_mp3", O_RDWR); if (afd < 0) { close(fd); perror("cannot open mp3 output device"); return -1; } fprintf(stderr,"MP3 PLAY\n"); ioctl(afd, AUDIO_START, 0); for (;;) { r = read(fd, buf, 64*1024); if (r <= 0) break; r = write(afd, buf, r); if (r < 0) break; } close(fd); close(afd); return 0; } int main(int argc, char **argv) { const char *fn = 0; int play = 1; unsigned channels = 1; unsigned rate = 44100; argc--; argv++; while (argc > 0) { if (!strcmp(argv[0],"-rec")) { play = 0; } else if (!strcmp(argv[0],"-play")) { play = 1; } else if (!strcmp(argv[0],"-stereo")) { channels = 2; } else if (!strcmp(argv[0],"-mono")) { channels = 1; } else if (!strcmp(argv[0],"-rate")) { argc--; argv++; if (argc == 0) { fprintf(stderr,"playwav: -rate requires a parameter\n"); return -1; } rate = atoi(argv[0]); } else { fn = argv[0]; } argc--; argv++; } if (fn == 0) { fn = play ? "/data/out.wav" : "/data/rec.wav"; } if (play) { const char *dot = strrchr(fn, '.'); if (dot && !strcmp(dot,".mp3")) { return mp3_play(fn); } else { return wav_play(fn); } } else { return wav_rec(fn, channels, rate); } return 0; }