/* ** 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. */ #define LOG_TAG "alsa_pcm" #define LOG_NDEBUG 1 #ifdef ANDROID /* definitions for Android logging */ #include <utils/Log.h> #include <cutils/properties.h> #else /* ANDROID */ #define strlcat g_strlcat #define strlcpy g_strlcpy #define ALOGI(...) fprintf(stdout, __VA_ARGS__) #define ALOGE(...) fprintf(stderr, __VA_ARGS__) #define ALOGV(...) fprintf(stderr, __VA_ARGS__) #endif /* ANDROID */ #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <stdarg.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <stdint.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <sys/time.h> #include <sys/poll.h> #include <linux/ioctl.h> #include <linux/types.h> #include "alsa_audio.h" #define __force #define __bitwise #define __user #define DEBUG 1 enum format_alias { S8 = 0, U8, S16_LE, S16_BE, U16_LE, U16_BE, S24_LE, S24_BE, U24_LE, U24_BE, S32_LE, S32_BE, U32_LE, U32_BE, FLOAT_LE, FLOAT_BE, FLOAT64_LE, FLOAT64_BE, IEC958_SUBFRAME_LE, IEC958_SUBFRAME_BE, MU_LAW, A_LAW, IMA_ADPCM, MPEG, GSM, SPECIAL = 31, S24_3LE, S24_3BE, U24_3LE, U24_3BE, S20_3LE, S20_3BE, U20_3LE, U20_3BE, S18_3LE, S18_3BE, U18_3LE, U18_3BE, FORMAT_LAST, }; const char *formats_list[][2] = { {"S8", "Signed 8 bit"}, {"U8", "Unsigned 8 bit"}, {"S16_LE", "Signed 16 bit Little Endian"}, {"S16_BE", "Signed 16 bit Big Endian"}, {"U16_LE", "Unsigned 16 bit Little Endian"}, {"U16_BE", "Unsigned 16 bit Big Endian"}, {"S24_LE", "Signed 24 bit Little Endian"}, {"S24_BE", "Signed 24 bit Big Endian"}, {"U24_LE", "Unsigned 24 bit Little Endian"}, {"U24_BE", "Unsigned 24 bit Big Endian"}, {"S32_LE", "Signed 32 bit Little Endian"}, {"S32_BE", "Signed 32 bit Big Endian"}, {"U32_LE", "Unsigned 32 bit Little Endian"}, {"U32_BE", "Unsigned 32 bit Big Endian"}, {"FLOAT_LE", "Float 32 bit Little Endian"}, {"FLOAT_BE", "Float 32 bit Big Endian"}, {"FLOAT64_LE", "Float 64 bit Little Endian"}, {"FLOAT64_BE", "Float 64 bit Big Endian"}, {"IEC958_SUBFRAME_LE", "IEC-958 Little Endian"}, {"IEC958_SUBFRAME_BE", "IEC-958 Big Endian"}, {"MU_LAW", "Mu-Law"}, {"A_LAW", "A-Law"}, {"IMA_ADPCM", "Ima-ADPCM"}, {"MPEG", "MPEG"}, {"GSM", "GSM"}, [31] = {"SPECIAL", "Special"}, {"S24_3LE", "Signed 24 bit Little Endian in 3bytes"}, {"S24_3BE", "Signed 24 bit Big Endian in 3bytes"}, {"U24_3LE", "Unsigned 24 bit Little Endian in 3bytes"}, {"U24_3BE", "Unsigned 24 bit Big Endian in 3bytes"}, {"S20_3LE", "Signed 20 bit Little Endian in 3bytes"}, {"S20_3BE", "Signed 20 bit Big Endian in 3bytes"}, {"U20_3LE", "Unsigned 20 bit Little Endian in 3bytes"}, {"U20_3BE", "Unsigned 20 bit Big Endian in 3bytes"}, {"S18_3LE", "Signed 18 bit Little Endian in 3bytes"}, {"S18_3BE", "Signed 18 bit Big Endian in 3bytes"}, {"U18_3LE", "Unsigned 18 bit Little Endian in 3bytes"}, {"U18_3BE", "Unsigned 18 bit Big Endian in 3bytes"}, }; int get_compressed_format(const char *format) { const char *ch = format; if (strcmp(ch, "MP3") == 0) { printf("MP3 is selected\n"); return FORMAT_MP3; } else if (strcmp(ch, "AC3_PASS_THROUGH") == 0) { printf("AC3 PASS THROUGH is selected\n"); return FORMAT_AC3_PASS_THROUGH; } else { printf("invalid format\n"); return -1; } return 0; } int get_format(const char* name) { int format; for (format = 0; format < FORMAT_LAST; format++) { if (formats_list[format][0] && strcasecmp(name, formats_list[format][0]) == 0) { ALOGV("format_names %s", name); return format; } } return -EINVAL; } const char *get_format_name(int format) { if ((format < FORMAT_LAST) && formats_list[format][0]) return formats_list[format][0]; return NULL; } const char *get_format_desc(int format) { if ((format < FORMAT_LAST) && formats_list[format][1]) return formats_list[format][1]; return NULL; } /* alsa parameter manipulation cruft */ #define PARAM_MAX SNDRV_PCM_HW_PARAM_LAST_INTERVAL static int oops(struct pcm *pcm, int e, const char *fmt, ...); static inline int param_is_mask(int p) { return (p >= SNDRV_PCM_HW_PARAM_FIRST_MASK) && (p <= SNDRV_PCM_HW_PARAM_LAST_MASK); } static inline int param_is_interval(int p) { return (p >= SNDRV_PCM_HW_PARAM_FIRST_INTERVAL) && (p <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL); } static inline struct snd_interval *param_to_interval(struct snd_pcm_hw_params *p, int n) { return &(p->intervals[n - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL]); } static inline struct snd_mask *param_to_mask(struct snd_pcm_hw_params *p, int n) { return &(p->masks[n - SNDRV_PCM_HW_PARAM_FIRST_MASK]); } void param_set_mask(struct snd_pcm_hw_params *p, int n, unsigned bit) { if (bit >= SNDRV_MASK_MAX) return; if (param_is_mask(n)) { struct snd_mask *m = param_to_mask(p, n); m->bits[0] = 0; m->bits[1] = 0; m->bits[bit >> 5] |= (1 << (bit & 31)); } } void param_set_min(struct snd_pcm_hw_params *p, int n, unsigned val) { if (param_is_interval(n)) { struct snd_interval *i = param_to_interval(p, n); i->min = val; } } void param_set_max(struct snd_pcm_hw_params *p, int n, unsigned val) { if (param_is_interval(n)) { struct snd_interval *i = param_to_interval(p, n); i->max = val; } } void param_set_int(struct snd_pcm_hw_params *p, int n, unsigned val) { if (param_is_interval(n)) { struct snd_interval *i = param_to_interval(p, n); i->min = val; i->max = val; i->integer = 1; } } void param_init(struct snd_pcm_hw_params *p) { int n; memset(p, 0, sizeof(*p)); for (n = SNDRV_PCM_HW_PARAM_FIRST_MASK; n <= SNDRV_PCM_HW_PARAM_LAST_MASK; n++) { struct snd_mask *m = param_to_mask(p, n); m->bits[0] = ~0; m->bits[1] = ~0; } for (n = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; n <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; n++) { struct snd_interval *i = param_to_interval(p, n); i->min = 0; i->max = ~0; } } /* debugging gunk */ #if DEBUG static const char *param_name[PARAM_MAX+1] = { [SNDRV_PCM_HW_PARAM_ACCESS] = "access", [SNDRV_PCM_HW_PARAM_FORMAT] = "format", [SNDRV_PCM_HW_PARAM_SUBFORMAT] = "subformat", [SNDRV_PCM_HW_PARAM_SAMPLE_BITS] = "sample_bits", [SNDRV_PCM_HW_PARAM_FRAME_BITS] = "frame_bits", [SNDRV_PCM_HW_PARAM_CHANNELS] = "channels", [SNDRV_PCM_HW_PARAM_RATE] = "rate", [SNDRV_PCM_HW_PARAM_PERIOD_TIME] = "period_time", [SNDRV_PCM_HW_PARAM_PERIOD_SIZE] = "period_size", [SNDRV_PCM_HW_PARAM_PERIOD_BYTES] = "period_bytes", [SNDRV_PCM_HW_PARAM_PERIODS] = "periods", [SNDRV_PCM_HW_PARAM_BUFFER_TIME] = "buffer_time", [SNDRV_PCM_HW_PARAM_BUFFER_SIZE] = "buffer_size", [SNDRV_PCM_HW_PARAM_BUFFER_BYTES] = "buffer_bytes", [SNDRV_PCM_HW_PARAM_TICK_TIME] = "tick_time", }; void param_dump(struct snd_pcm_hw_params *p) { int n; for (n = SNDRV_PCM_HW_PARAM_FIRST_MASK; n <= SNDRV_PCM_HW_PARAM_LAST_MASK; n++) { struct snd_mask *m = param_to_mask(p, n); ALOGV("%s = %08x%08x\n", param_name[n], m->bits[1], m->bits[0]); } for (n = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; n <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; n++) { struct snd_interval *i = param_to_interval(p, n); ALOGV("%s = (%d,%d) omin=%d omax=%d int=%d empty=%d\n", param_name[n], i->min, i->max, i->openmin, i->openmax, i->integer, i->empty); } ALOGV("info = %08x\n", p->info); ALOGV("msbits = %d\n", p->msbits); ALOGV("rate = %d/%d\n", p->rate_num, p->rate_den); ALOGV("fifo = %d\n", (int) p->fifo_size); } static void info_dump(struct snd_pcm_info *info) { ALOGV("device = %d\n", info->device); ALOGV("subdevice = %d\n", info->subdevice); ALOGV("stream = %d\n", info->stream); ALOGV("card = %d\n", info->card); ALOGV("id = '%s'\n", info->id); ALOGV("name = '%s'\n", info->name); ALOGV("subname = '%s'\n", info->subname); ALOGV("dev_class = %d\n", info->dev_class); ALOGV("dev_subclass = %d\n", info->dev_subclass); ALOGV("subdevices_count = %d\n", info->subdevices_count); ALOGV("subdevices_avail = %d\n", info->subdevices_avail); } #else void param_dump(struct snd_pcm_hw_params *p) {} static void info_dump(struct snd_pcm_info *info) {} #endif int param_set_hw_refine(struct pcm *pcm, struct snd_pcm_hw_params *params) { if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_REFINE, params)) { ALOGE("SNDRV_PCM_IOCTL_HW_REFINE failed"); return -EPERM; } return 0; } int param_set_hw_params(struct pcm *pcm, struct snd_pcm_hw_params *params) { if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, params)) { return -EPERM; } pcm->hw_p = params; return 0; } int param_set_sw_params(struct pcm *pcm, struct snd_pcm_sw_params *sparams) { if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SW_PARAMS, sparams)) { return -EPERM; } pcm->sw_p = sparams; return 0; } int pcm_buffer_size(struct snd_pcm_hw_params *params) { struct snd_interval *i = param_to_interval(params, SNDRV_PCM_HW_PARAM_BUFFER_BYTES); ALOGV("%s = (%d,%d) omin=%d omax=%d int=%d empty=%d\n", param_name[SNDRV_PCM_HW_PARAM_BUFFER_BYTES], i->min, i->max, i->openmin, i->openmax, i->integer, i->empty); return i->min; } int pcm_period_size(struct snd_pcm_hw_params *params) { struct snd_interval *i = param_to_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_BYTES); ALOGV("%s = (%d,%d) omin=%d omax=%d int=%d empty=%d\n", param_name[SNDRV_PCM_HW_PARAM_PERIOD_BYTES], i->min, i->max, i->openmin, i->openmax, i->integer, i->empty); return i->min; } const char* pcm_error(struct pcm *pcm) { return pcm->error; } static int oops(struct pcm *pcm, int e, const char *fmt, ...) { va_list ap; int sz; va_start(ap, fmt); vsnprintf(pcm->error, PCM_ERROR_MAX, fmt, ap); va_end(ap); sz = strnlen(pcm->error, PCM_ERROR_MAX); if (errno) snprintf(pcm->error + sz, PCM_ERROR_MAX - sz, ": %s", strerror(e)); return -1; } long pcm_avail(struct pcm *pcm) { struct snd_pcm_sync_ptr *sync_ptr = pcm->sync_ptr; if (pcm->flags & DEBUG_ON) { ALOGV("hw_ptr = %d buf_size = %d appl_ptr = %d\n", sync_ptr->s.status.hw_ptr, pcm->buffer_size, sync_ptr->c.control.appl_ptr); } if (pcm->flags & PCM_IN) { long avail = sync_ptr->s.status.hw_ptr - sync_ptr->c.control.appl_ptr; if (avail < 0) avail += pcm->sw_p->boundary; return avail; } else { long avail = sync_ptr->s.status.hw_ptr - sync_ptr->c.control.appl_ptr + ((pcm->flags & PCM_MONO) ? pcm->buffer_size/2 : pcm->buffer_size/4); if (avail < 0) avail += pcm->sw_p->boundary; else if ((unsigned long) avail >= pcm->sw_p->boundary) avail -= pcm->sw_p->boundary; return avail; } } int sync_ptr(struct pcm *pcm) { int err; err = ioctl(pcm->fd, SNDRV_PCM_IOCTL_SYNC_PTR, pcm->sync_ptr); if (err < 0) { err = errno; ALOGE("SNDRV_PCM_IOCTL_SYNC_PTR failed %d \n", err); return err; } return 0; } int mmap_buffer(struct pcm *pcm) { int err, i; char *ptr; unsigned size; struct snd_pcm_channel_info ch; int channels = (pcm->flags & PCM_MONO) ? 1 : 2; size = pcm->buffer_size; if (pcm->flags & DEBUG_ON) ALOGV("size = %d\n", size); pcm->addr = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, pcm->fd, 0); if (pcm->addr) return 0; else return -errno; } /* * Destination offset would be mod of total data written * (application pointer) and the buffer size of the driver. * Hence destination address would be base address(pcm->addr) + * destination offset. */ u_int8_t *dst_address(struct pcm *pcm) { unsigned long pcm_offset = 0; struct snd_pcm_sync_ptr *sync_ptr = pcm->sync_ptr; unsigned int appl_ptr = 0; appl_ptr = (pcm->flags & PCM_MONO) ? sync_ptr->c.control.appl_ptr*2 : sync_ptr->c.control.appl_ptr*4; pcm_offset = (appl_ptr % (unsigned long)pcm->buffer_size); return pcm->addr + pcm_offset; } int mmap_transfer(struct pcm *pcm, void *data, unsigned offset, long frames) { struct snd_pcm_sync_ptr *sync_ptr = pcm->sync_ptr; unsigned size; u_int8_t *dst_addr, *mmaped_addr; u_int8_t *src_addr = data; int channels = (pcm->flags & PCM_MONO) ? 1 : 2; dst_addr = dst_address(pcm); frames = frames * channels *2 ; while (frames-- > 0) { *(u_int8_t*)dst_addr = *(const u_int8_t*)src_addr; src_addr++; dst_addr++; } return 0; } int mmap_transfer_capture(struct pcm *pcm, void *data, unsigned offset, long frames) { struct snd_pcm_sync_ptr *sync_ptr = pcm->sync_ptr; unsigned long pcm_offset = 0; unsigned size; u_int8_t *dst_addr, *mmaped_addr; u_int8_t *src_addr; int channels = (pcm->flags & PCM_MONO) ? 1 : 2; unsigned int tmp = (pcm->flags & PCM_MONO) ? sync_ptr->c.control.appl_ptr*2 : sync_ptr->c.control.appl_ptr*4; pcm_offset = (tmp % (unsigned long)pcm->buffer_size); dst_addr = data; src_addr = pcm->addr + pcm_offset; frames = frames * channels *2 ; while (frames-- > 0) { *(u_int8_t*)dst_addr = *(const u_int8_t*)src_addr; src_addr++; dst_addr++; } return 0; } int pcm_prepare(struct pcm *pcm) { if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE)) { ALOGE("cannot prepare channel: errno =%d\n", -errno); return -errno; } pcm->running = 1; return 0; } static int pcm_write_mmap(struct pcm *pcm, void *data, unsigned count) { long frames; int err; int bytes_written; frames = (pcm->flags & PCM_MONO) ? (count / 2) : (count / 4); pcm->sync_ptr->flags = SNDRV_PCM_SYNC_PTR_APPL | SNDRV_PCM_SYNC_PTR_AVAIL_MIN; err = sync_ptr(pcm); if (err == EPIPE) { ALOGE("Failed in sync_ptr\n"); /* we failed to make our window -- try to restart */ pcm->underruns++; pcm->running = 0; pcm_prepare(pcm); } pcm->sync_ptr->c.control.appl_ptr += frames; pcm->sync_ptr->flags = 0; err = sync_ptr(pcm); if (err == EPIPE) { ALOGE("Failed in sync_ptr 2 \n"); /* we failed to make our window -- try to restart */ pcm->underruns++; pcm->running = 0; pcm_prepare(pcm); } bytes_written = pcm->sync_ptr->c.control.appl_ptr - pcm->sync_ptr->s.status.hw_ptr; if ((bytes_written >= pcm->sw_p->start_threshold) && (!pcm->start)) { if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_START)) { err = -errno; if (errno == EPIPE) { ALOGE("Failed in SNDRV_PCM_IOCTL_START\n"); /* we failed to make our window -- try to restart */ pcm->underruns++; pcm->running = 0; pcm_prepare(pcm); } else { ALOGE("Error no %d \n", errno); return -errno; } } else { ALOGD(" start\n"); pcm->start = 1; } } return 0; } static int pcm_write_nmmap(struct pcm *pcm, void *data, unsigned count) { struct snd_xferi x; int channels = (pcm->flags & PCM_MONO) ? 1 : ((pcm->flags & PCM_5POINT1)? 6 : 2 ); if (pcm->flags & PCM_IN) return -EINVAL; x.buf = data; x.frames = (count / (channels * 2)) ; for (;;) { if (!pcm->running) { if (pcm_prepare(pcm)) return -errno; } if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) { if (errno == EPIPE) { /* we failed to make our window -- try to restart */ ALOGE("Underrun Error\n"); pcm->underruns++; pcm->running = 0; continue; } return -errno; } if (pcm->flags & DEBUG_ON) ALOGV("Sent frame\n"); return 0; } } int pcm_write(struct pcm *pcm, void *data, unsigned count) { if (pcm->flags & PCM_MMAP) return pcm_write_mmap(pcm, data, count); else return pcm_write_nmmap(pcm, data, count); } int pcm_read(struct pcm *pcm, void *data, unsigned count) { struct snd_xferi x; if (!(pcm->flags & PCM_IN)) return -EINVAL; x.buf = data; if (pcm->flags & PCM_MONO) { x.frames = (count / 2); } else if (pcm->flags & PCM_QUAD) { x.frames = (count / 8); } else if (pcm->flags & PCM_5POINT1) { x.frames = (count / 12); } else { x.frames = (count / 4); } for (;;) { if (!pcm->running) { if (pcm_prepare(pcm)) return -errno; if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_START)) { ALOGE("Arec:SNDRV_PCM_IOCTL_START failed\n"); return -errno; } pcm->running = 1; } if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_READI_FRAMES, &x)) { if (errno == EPIPE) { /* we failed to make our window -- try to restart */ ALOGE("Arec:Overrun Error\n"); pcm->underruns++; pcm->running = 0; continue; } ALOGE("Arec: error%d\n", errno); return -errno; } return 0; } } static struct pcm bad_pcm = { .fd = -1, }; static int enable_timer(struct pcm *pcm) { pcm->timer_fd = open("/dev/snd/timer", O_RDWR | O_NONBLOCK); if (pcm->timer_fd < 0) { close(pcm->fd); ALOGE("cannot open timer device 'timer'"); return &bad_pcm; } int arg = 1; struct snd_timer_params timer_param; struct snd_timer_select sel; if (ioctl(pcm->timer_fd, SNDRV_TIMER_IOCTL_TREAD, &arg) < 0) { ALOGE("extended read is not supported (SNDRV_TIMER_IOCTL_TREAD)\n"); } memset(&sel, 0, sizeof(sel)); sel.id.dev_class = SNDRV_TIMER_CLASS_PCM; sel.id.dev_sclass = SNDRV_TIMER_SCLASS_NONE; sel.id.card = pcm->card_no; sel.id.device = pcm->device_no; if (pcm->flags & PCM_IN) sel.id.subdevice = 1; else sel.id.subdevice = 0; if (pcm->flags & DEBUG_ON) { ALOGD("sel.id.dev_class= %d\n", sel.id.dev_class); ALOGD("sel.id.dev_sclass = %d\n", sel.id.dev_sclass); ALOGD("sel.id.card = %d\n", sel.id.card); ALOGD("sel.id.device = %d\n", sel.id.device); ALOGD("sel.id.subdevice = %d\n", sel.id.subdevice); } if (ioctl(pcm->timer_fd, SNDRV_TIMER_IOCTL_SELECT, &sel) < 0) { ALOGE("SNDRV_TIMER_IOCTL_SELECT failed.\n"); close(pcm->timer_fd); close(pcm->fd); return &bad_pcm; } memset(&timer_param, 0, sizeof(struct snd_timer_params)); timer_param.flags |= SNDRV_TIMER_PSFLG_AUTO; timer_param.ticks = 1; timer_param.filter = (1<<SNDRV_TIMER_EVENT_MSUSPEND) | (1<<SNDRV_TIMER_EVENT_MRESUME) | (1<<SNDRV_TIMER_EVENT_TICK); if (ioctl(pcm->timer_fd, SNDRV_TIMER_IOCTL_PARAMS, &timer_param)< 0) { ALOGE("SNDRV_TIMER_IOCTL_PARAMS failed\n"); } if (ioctl(pcm->timer_fd, SNDRV_TIMER_IOCTL_START) < 0) { close(pcm->timer_fd); ALOGE("SNDRV_TIMER_IOCTL_START failed\n"); } return 0; } static int disable_timer(struct pcm *pcm) { if (pcm == &bad_pcm) return 0; if (ioctl(pcm->timer_fd, SNDRV_TIMER_IOCTL_STOP) < 0) ALOGE("SNDRV_TIMER_IOCTL_STOP failed\n"); return close(pcm->timer_fd); } int pcm_close(struct pcm *pcm) { if (pcm == &bad_pcm) return 0; if (pcm->flags & PCM_MMAP) { disable_timer(pcm); if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_DROP) < 0) { ALOGE("Reset failed"); } if (munmap(pcm->addr, pcm->buffer_size)) ALOGE("munmap failed"); if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_FREE) < 0) { ALOGE("HW_FREE failed"); } } if (pcm->fd >= 0) close(pcm->fd); pcm->running = 0; pcm->buffer_size = 0; pcm->fd = -1; if (pcm->sw_p) free(pcm->sw_p); if (pcm->hw_p) free(pcm->hw_p); if (pcm->sync_ptr) free(pcm->sync_ptr); free(pcm); return 0; } struct pcm *pcm_open(unsigned flags, char *device) { char dname[19]; struct pcm *pcm; struct snd_pcm_info info; struct snd_pcm_hw_params params; struct snd_pcm_sw_params sparams; unsigned period_sz; unsigned period_cnt; char *tmp; if (flags & DEBUG_ON) { ALOGV("pcm_open(0x%08x)",flags); ALOGV("device %s\n",device); } pcm = calloc(1, sizeof(struct pcm)); if (!pcm) return &bad_pcm; tmp = device+4; if ((strncmp(device, "hw:",3) != 0) || (strncmp(tmp, ",",1) != 0)){ ALOGE("Wrong device fromat\n"); free(pcm); return -EINVAL; } if (flags & PCM_IN) { strlcpy(dname, "/dev/snd/pcmC", sizeof(dname)); tmp = device+3; strlcat(dname, tmp, (2+strlen(dname))) ; pcm->card_no = atoi(tmp); strlcat(dname, "D", (sizeof("D")+strlen(dname))); tmp = device+5; pcm->device_no = atoi(tmp); /* should be safe to assume pcm dev ID never exceed 99 */ if (pcm->device_no > 9) strlcat(dname, tmp, (3+strlen(dname))); else strlcat(dname, tmp, (2+strlen(dname))); strlcat(dname, "c", (sizeof("c")+strlen(dname))); } else { strlcpy(dname, "/dev/snd/pcmC", sizeof(dname)); tmp = device+3; strlcat(dname, tmp, (2+strlen(dname))) ; pcm->card_no = atoi(tmp); strlcat(dname, "D", (sizeof("D")+strlen(dname))); tmp = device+5; pcm->device_no = atoi(tmp); /* should be safe to assume pcm dev ID never exceed 99 */ if (pcm->device_no > 9) strlcat(dname, tmp, (3+strlen(dname))); else strlcat(dname, tmp, (2+strlen(dname))); strlcat(dname, "p", (sizeof("p")+strlen(dname))); } if (pcm->flags & DEBUG_ON) ALOGV("Device name %s\n", dname); pcm->sync_ptr = calloc(1, sizeof(struct snd_pcm_sync_ptr)); if (!pcm->sync_ptr) { free(pcm); return &bad_pcm; } pcm->flags = flags; pcm->fd = open(dname, O_RDWR|O_NONBLOCK); if (pcm->fd < 0) { free(pcm->sync_ptr); free(pcm); ALOGE("cannot open device '%s', errno %d", dname, errno); return &bad_pcm; } if (fcntl(pcm->fd, F_SETFL, fcntl(pcm->fd, F_GETFL) & ~O_NONBLOCK) < 0) { close(pcm->fd); free(pcm->sync_ptr); free(pcm); ALOGE("failed to change the flag, errno %d", errno); return &bad_pcm; } if (pcm->flags & PCM_MMAP) enable_timer(pcm); if (pcm->flags & DEBUG_ON) ALOGV("pcm_open() %s\n", dname); if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info)) { ALOGE("cannot get info - %s", dname); } if (pcm->flags & DEBUG_ON) info_dump(&info); return pcm; } int pcm_ready(struct pcm *pcm) { return pcm->fd >= 0; }