You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
379 lines
8.8 KiB
379 lines
8.8 KiB
/* 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; |
|
}
|
|
|