/* pcm.c ** ** Copyright 2011, The Android Open Source Project ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** * Neither the name of The Android Open Source Project nor the names of ** its contributors may be used to endorse or promote products derived ** from this software without specific prior written permission. ** ** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR ** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER ** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH ** DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define __force #define __bitwise #define __user #include #include #define PARAM_MAX SNDRV_PCM_HW_PARAM_LAST_INTERVAL #define SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP (1<<2) #define LG_MAX 0x7fffffff #define CLICK_VOICE_KEY_PATH "./resource/key.wav" #define CLICK_VOICE_CAP_PATH "./resource/capture.wav" #define CLICK_VOICE_BUF_SIZE (4*1024) #define CLICK_VOICE_FILE_NUM (2) #define ID_RIFF 0x46464952 #define ID_WAVE 0x45564157 #define ID_FMT 0x20746d66 #define ID_DATA 0x61746164 struct wav_header { unsigned int riff_id; unsigned int riff_sz; unsigned int riff_fmt; unsigned int fmt_id; unsigned int fmt_sz; unsigned short audio_format; unsigned short num_channels; unsigned int sample_rate; unsigned int byte_rate; unsigned short block_align; unsigned short bits_per_sample; unsigned int data_id; unsigned int data_sz; }; #define FORMAT_PCM 1 struct click_voice_st { int click_voice_fds[CLICK_VOICE_FILE_NUM]; char *click_voice_buf; int click_voice_init_flag; sem_t click_voice_sem; pthread_mutex_t click_voice_lock; pthread_t click_voice_tid; }; static struct click_voice_st click_voice_context; //static pthread_mutex_t pcm_write_lock = PTHREAD_MUTEX_INITIALIZER; //static pthread_mutex_t click_lock = PTHREAD_MUTEX_INITIALIZER; //static pthread_cond_t pcm_write_cond = PTHREAD_COND_INITIALIZER; struct pcm *pcm_p = NULL; struct pcm *pcm_c = NULL; //static int key_voice_file = -1; //static unsigned int key_clicks = 0; static sem_t sem_pcm_p; extern int pcm_resume(struct pcm *pcm); static void tinymix_set_value(struct mixer *mixer, unsigned int id, char *string) { struct mixer_ctl *ctl; enum mixer_ctl_type type; unsigned int num_values; unsigned int i; ctl = mixer_get_ctl(mixer, id); type = mixer_ctl_get_type(ctl); num_values = mixer_ctl_get_num_values(ctl); if (isdigit(string[0])) { int value = atoi(string); for (i = 0; i < num_values; i++) { if (mixer_ctl_set_value(ctl, i, value)) { fprintf(stderr, "Error: invalid value\n"); return; } } } else { if (type == MIXER_CTL_TYPE_ENUM) { if (mixer_ctl_set_enum_by_string(ctl, string)) fprintf(stderr, "Error: invalid enum value\n"); } else { fprintf(stderr, "Error: only enum types can be set with strings\n"); } } } static void tinymix_get_value(struct mixer *mixer, unsigned int id, int *value) { struct mixer_ctl *ctl; enum mixer_ctl_type type; if(!value) { printf("invalid value pointer\n"); return ; } if(id >= mixer_get_num_ctls(mixer)) { fprintf(stderr, "Invalid mixer id\n"); return; } ctl = mixer_get_ctl(mixer, id); type = mixer_ctl_get_type(ctl); if(type == MIXER_CTL_TYPE_INT) { *value = mixer_ctl_get_value(ctl, 0); } if(type == MIXER_CTL_TYPE_BOOL) { *value = mixer_ctl_get_value(ctl, 0); } } #if 0 static void open_key_voice(void) { key_voice_file = open(VOICE_FILE, O_RDWR); if(key_voice_file == -1) { fprintf(stderr, "open key voice file fail\n"); return ; } lseek(key_voice_file, 44, SEEK_SET); } static void close_key_voice(void) { if(key_voice_file >= 0) close(key_voice_file); key_voice_file = -1; } static void click_key_voice(void) { // pthread_mutex_lock(&click_lock); key_clicks++; // pthread_mutex_unlock(&click_lock); // pthread_cond_signal(&pcm_write_cond); } #define ARCH_ADD(p,a) ((p) += (a)) static void alsa_mix_16(short data1,short data2,short *date_mix) { int sample, old_sample,sum; sample = data1; old_sample = data2; sum = data2; if(*date_mix == 0) sample -= old_sample; ARCH_ADD(sum,sample); do{ old_sample = sum; if(old_sample > 0x7fff) sample = 0x7fff; else if(old_sample < -0x8000) sample = -0x8000; else sample = old_sample; *date_mix = sample; }while(0); } static void convert_out(char *src1, char *src2, char *dst, int size) { int i = 0, cnt = size/2; if(!cnt) { fprintf(stderr, "invalid size\n"); return ; } for(i = 0; i < cnt; i++){ alsa_mix_16(((short *)src1)[i],((short *)src2)[i], ((short *)dst)+i); } } static void add_key_voice(void *data, unsigned int count) { char *buf; int cnt; buf = malloc(count); if(!buf) { fprintf(stderr, "malloc err\n"); return ; } cnt = read(key_voice_file, buf, count); if(cnt < 0) { fprintf(stderr, "read voice file error\n"); return ; } if(cnt < count) { pthread_mutex_lock(&click_lock); key_clicks--; pthread_mutex_unlock(&click_lock); lseek(key_voice_file, 44, SEEK_SET); } convert_out(data, buf, data, cnt); free(buf); } void play_key_voice(void) { char *buf; int cnt, ret; buf = malloc(1024); if(!buf) { fprintf(stderr, "malloc err\n"); return ; } cnt = read(key_voice_file, buf, 1024); if(cnt < 0) { fprintf(stderr, "read voice file error\n"); return ; } if(cnt < 1024) { pthread_mutex_lock(&click_lock); key_clicks--; pthread_mutex_unlock(&click_lock); lseek(key_voice_file, 44, SEEK_SET); } ret = pcm_write(pcm_p, buf, cnt); if(ret != 0) { fprintf(stderr, "paly voice err\n"); } free(buf); } static int mixer_play(struct play_queue *queue) { int ret; if(!queue || !queue->size) { fprintf(stderr, "mixer play error queue\n"); return -1; } add_key_voice(queue->buf, queue->size); ret = pcm_write(pcm_p, queue->buf, queue->size); return ret; } static void *key_voice_thread_function(void *pvoid) { #if 0 struct play_queue *queue; int ret; while(1) { pthread_mutex_lock(&pcm_write_lock); while(!is_buf_ready()) pthread_cond_wait(&pcm_write_cond, &pcm_write_lock); pthread_mutex_unlock(&pcm_write_lock); if(!is_empty_queue() && key_clicks > 0) { /* mixer play */ queue = get_play_queue(); if(queue) { mixer_play(queue); destroy_queue(queue); queue = NULL; } }else if(!is_empty_queue()) {/* normal play */ queue = get_play_queue(); if(queue) { ret = pcm_write(pcm_p, queue->buf, queue->size); destroy_queue(queue); queue = NULL; } }else if(key_clicks > 0) { /* key void only */ play_key_voice(); }else { printf("paly nothing\n"); } } #endif return NULL; } #endif #if 0 static void key_voice_init(void) { int err = 0; open_key_voice(); /* create pthread */ err = pthread_create(&key_voice_tid, NULL, key_voice_thread_function, NULL); if(err != 0) { fprintf(stderr, "create pthread error!\n"); goto fail; } return ; fail: close_key_voice(); } static void key_voice_exit(void) { pthread_cancel(key_voice_tid); close_key_voice(); } #endif static int _pcm_open(unsigned int flags, struct pcm_config *cfg) { struct pcm *pcm = NULL; struct mixer *mixer; if(flags & PCM_OUT) { if(click_voice_context.click_voice_init_flag) { if(flags&PCM_OUT_WAIT) { sem_wait(&sem_pcm_p); }else if(flags&PCM_OUT_TRY) { if(sem_trywait(&sem_pcm_p) != 0) { return -1; } } } if(pcm_p) { printf("The play device has already creat!\n"); if(click_voice_context.click_voice_init_flag) sem_post(&sem_pcm_p); return -1; } pcm = pcm_open(0, 0, flags & (~PCM_IN), cfg); if (!pcm || !pcm_is_ready(pcm)) { fprintf(stderr, "Unable to open PCM device %d (%s)\n", 0, pcm_get_error(pcm)); if(click_voice_context.click_voice_init_flag) sem_post(&sem_pcm_p); return -1; } else { pcm_p = pcm; // printf("create pcm out suceessfully\n"); } } else if(flags & PCM_IN) { if(pcm_c){ printf("The catpure device has already creat!\n"); return -1; } pcm = pcm_open(0, 0, flags & (~PCM_OUT), cfg); if (!pcm || !pcm_is_ready(pcm)) { fprintf(stderr, "Unable to open PCM device (%s)\n", pcm_get_error(pcm)); return -1; } else { pcm_c = pcm; } mixer = mixer_open(0); if (!mixer) { fprintf(stderr, "Failed to open mixer\n"); return 0; } tinymix_set_value(mixer, 35, "1"); // tinymix_set_value(mixer, 27, "1"); mixer_close(mixer); } else { fprintf(stderr, "Invalid param\n"); return -1; } return 0; } static int click_voice_play_file(char *buf, int fd) { struct wav_header play_header; struct pcm_config cfg; ssize_t size = 0; if(!buf) return -1; lseek(fd, 0, SEEK_SET); size = read(fd, &play_header, sizeof(struct wav_header)); if(size != sizeof(struct wav_header)) { printf("%s:read voice file error\n", __func__); return -1; } /* click key voice */ cfg.channels = play_header.num_channels; cfg.rate = play_header.sample_rate; cfg.period_size = 512; cfg.period_count = 2; cfg.format = play_header.bits_per_sample; cfg.start_threshold = 0; cfg.stop_threshold = 0; cfg.silence_threshold = 0; if(init_pcm(PCM_OUT|PCM_OUT_TRY, &cfg)) { printf("%s:open pcm error\n", __func__); return -1; } lseek(fd, 44, SEEK_SET); while((size = read(fd, buf, CLICK_VOICE_BUF_SIZE)) > 0) { pcm_write_api(buf, (unsigned int)size); } uninit_pcm(PCM_OUT); return 0; } static void *voice_thread_function(void *pvoid) { struct click_voice_st *pclick_voice_ctx = (struct click_voice_st *)pvoid; int i; while(1) { sem_wait(&pclick_voice_ctx->click_voice_sem); if(!pclick_voice_ctx->click_voice_init_flag) { break; } for(i = 0; i < CLICK_VOICE_FILE_NUM; i++) { if(pclick_voice_ctx->click_voice_fds[i] >= 0) { click_voice_play_file(pclick_voice_ctx->click_voice_buf, pclick_voice_ctx->click_voice_fds[i]); close(pclick_voice_ctx->click_voice_fds[i]); pthread_mutex_lock(&click_voice_context.click_voice_lock); pclick_voice_ctx->click_voice_fds[i] = -1; pthread_mutex_unlock(&click_voice_context.click_voice_lock); } } } return NULL; } int audio_module_init(void) { int ret = 0; int i; sem_init(&click_voice_context.click_voice_sem, 0, 0); pthread_mutex_init(&click_voice_context.click_voice_lock, NULL); click_voice_context.click_voice_buf = (char *)malloc(CLICK_VOICE_BUF_SIZE); if(!click_voice_context.click_voice_buf) { printf("%s:malloc error\n", __func__); return -1; } sem_init(&sem_pcm_p, 0, 1); sem_init(&click_voice_context.click_voice_sem, 0, 1); ret = pthread_create(&click_voice_context.click_voice_tid, NULL, voice_thread_function, &click_voice_context); if(ret != 0) { pthread_mutex_destroy(&click_voice_context.click_voice_lock); sem_destroy(&click_voice_context.click_voice_sem); free(click_voice_context.click_voice_buf); printf("%s:create pthread error\n", __func__); return -1; } for(i = 0; i < CLICK_VOICE_FILE_NUM; i++) { click_voice_context.click_voice_fds[i] = -1; } click_voice_context.click_voice_init_flag = 1; return 0; } int audio_module_uninit(void) { if(!click_voice_context.click_voice_init_flag) return 1; click_voice_context.click_voice_init_flag = 0; sem_post(&click_voice_context.click_voice_sem); pthread_join(click_voice_context.click_voice_tid, NULL); pthread_mutex_destroy(&click_voice_context.click_voice_lock); sem_destroy(&click_voice_context.click_voice_sem); sem_destroy(&sem_pcm_p); free(click_voice_context.click_voice_buf); memset(&click_voice_context, 0, sizeof(click_voice_context)); return 0; } int play_sound(const char *file_path) { int i; if(!click_voice_context.click_voice_init_flag) { return -1; } if(!file_path) { printf("%s:invalid file path\n", __func__); return -1; } for(i = 0; i < CLICK_VOICE_FILE_NUM; i++) { if(click_voice_context.click_voice_fds[i] == -1) { break; } } if(i >= CLICK_VOICE_FILE_NUM) { return -1; } pthread_mutex_lock(&click_voice_context.click_voice_lock); click_voice_context.click_voice_fds[i] = open(file_path, O_RDONLY); if(click_voice_context.click_voice_fds[i] == -1) { pthread_mutex_unlock(&click_voice_context.click_voice_lock); printf("%s:open voice file error\n", __func__); return -1; } pthread_mutex_unlock(&click_voice_context.click_voice_lock); sem_post(&click_voice_context.click_voice_sem); return 0; } int init_pcm(unsigned int flags, struct pcm_config *cfg) { struct pcm_config *config = NULL; int ret = 0; if(!cfg) { config = calloc(1, sizeof(struct pcm)); if(!config) { fprintf(stderr, "fail to allocate config memory\n"); return -1; } config->channels = 2; config->rate = 8000; config->period_size = 512; config->period_count = 3; config->format = PCM_FORMAT_S16_LE; config->start_threshold = 0; config->stop_threshold = 0; config->silence_threshold = 0; cfg = config; } if(flags & PCM_IN) ret = _pcm_open(PCM_IN, cfg); if(flags & PCM_OUT) { ret = _pcm_open(flags, cfg); // printf("open out pcm ok\n"); // key_voice_init(); } // printf("create pcm ok:%p %p\n", pcm_p, pcm_c); if(config) free(config); return ret; } int uninit_pcm(unsigned int flags) { struct pcm *pcm = NULL; struct mixer *mixer; int ret = 0; if((flags & PCM_OUT) && (flags & PCM_IN)) { printf("You must close only one device!\n"); return -1; } if(flags & PCM_OUT){ if(!pcm_p) { printf("The play device has already close!\n"); return -1; } pcm = pcm_p; pcm_p = NULL; // key_voice_exit(); }else if(flags & PCM_IN) { if(!pcm_c) { printf("The catpure device has already close!\n"); return -1; } pcm = pcm_c; pcm_c = NULL; mixer = mixer_open(0); if (!mixer) { fprintf(stderr, "Failed to open mixer\n"); return -1; } tinymix_set_value(mixer, 35, "0"); tinymix_set_value(mixer, 27, "0"); mixer_close(mixer); } ret = pcm_close(pcm); if(!ret && (flags&PCM_OUT)) { if(click_voice_context.click_voice_init_flag) sem_post(&sem_pcm_p); } // printf("close pcm\n"); return ret; } int pcm_write_api(void *data, unsigned int count) { #if 1 int res =0; if(!pcm_p) { fprintf(stderr, "You must creat play pcm firstly\n"); return -1; } if(!data) { fprintf(stderr, "Invalid data pointer\n"); return -1; } do { res = pcm_write(pcm_p, data, count); if (res != 0) { printf("pcm write fail:%s res:%d ESTRPIPE:%d\n", strerror(errno),res,ESTRPIPE); } if (res == -EINTR) { /* nothing to do */ res = 0; } else if (res == -EPIPE) { /* suspend */ printf("MSGTR_AO_ALSA_PcmInSuspendModeTryingResume\n"); while ((res = pcm_resume(pcm_p)) == -EAGAIN) sleep(1); } if (res < 0) { printf("MSGTR_AO_ALSA_WriteError\n"); if ((res = pcm_prepare(pcm_p)) < 0) { printf("MSGTR_AO_ALSA_PcmPrepareError\n"); return -1; //break; } } } while (res != 0); return 0; #else int ret = 0; struct play_queue *queue; if(!pcm_p) { fprintf(stderr, "You must creat play pcm firstly\n"); return -1; } if(!data) { fprintf(stderr, "Invalid data pointer\n"); return -1; } queue = make_a_queue(data, count); if(!queue) { return -1; } put_play_queue(queue); return ret; #endif } int pcm_read_api(void *data, unsigned int count) { if(!pcm_c) { fprintf(stderr, "You must creat capture pcm firstly\n"); return -1; } return pcm_read(pcm_c, data, count); } int audio_set_play_volume(int volume) { struct mixer *mixer; char buf[10]; if(volume > 63) volume = 63; if(volume < 0) volume = 0; mixer = mixer_open(0); if (!mixer) { fprintf(stderr, "Failed to open mixer\n"); return -1; } snprintf(buf, 10, "%d", volume); tinymix_set_value(mixer, 22, buf); mixer_close(mixer); return 0; } int audio_get_play_volume(int *value) { struct mixer *mixer; if(!value) { printf("%s:invalid value pointer\n", __func__); return -1; } mixer = mixer_open(0); if (!mixer) { fprintf(stderr, "Failed to open mixer\n"); return -1; } tinymix_get_value(mixer, 22, value); mixer_close(mixer); return 0; } int audio_set_rec_volume(int volume) { struct mixer *mixer; char buf[10]; if(volume > 7) volume = 7; if(volume < 0) volume = 0; mixer = mixer_open(0); if (!mixer) { fprintf(stderr, "Failed to open mixer\n"); return -1; } snprintf(buf, 10, "%d", volume); tinymix_set_value(mixer, 36, buf); mixer_close(mixer); return 0; } int audio_get_rec_volume(int *value) { struct mixer *mixer; if(!value) { printf("%s:invalid value pointer\n", __func__); return -1; } mixer = mixer_open(0); if (!mixer) { fprintf(stderr, "Failed to open mixer\n"); return -1; } tinymix_get_value(mixer, 36, value); mixer_close(mixer); return 0; } int audio_set_rec_mute(int is_mute) { struct mixer *mixer; int value = -1; mixer = mixer_open(0); if (!mixer) { fprintf(stderr, "Failed to open mixer\n"); return -1; } tinymix_get_value(mixer, 27, &value); if(is_mute) { if(!value) { mixer_close(mixer); return 0; } tinymix_set_value(mixer, 27, "0"); /* mute */ } else { if(value) { mixer_close(mixer); return 0; } tinymix_set_value(mixer, 27, "1"); /* unmute */ } mixer_close(mixer); return 0; } int audio_get_rec_mute(void) { struct mixer *mixer; int is_mute = -1; mixer = mixer_open(0); if (!mixer) { fprintf(stderr, "Failed to open mixer\n"); return -1; } tinymix_get_value(mixer, 27, &is_mute); mixer_close(mixer); if(is_mute == 1) { return 0; }else if(is_mute == 0) { return 1; } return -1; } int audio_set_play_mute(int is_mute) { struct mixer *mixer; int is_mute_r = -1, is_mute_l = -1; mixer = mixer_open(0); if (!mixer) { fprintf(stderr, "Failed to open mixer\n"); return -1; } tinymix_get_value(mixer, 5, &is_mute_r); tinymix_get_value(mixer, 6, &is_mute_l); if(is_mute) { if(is_mute_r != 0) tinymix_set_value(mixer, 5, "0"); if(is_mute_l != 0) tinymix_set_value(mixer, 6, "0"); } else { if(is_mute_r != 1) tinymix_set_value(mixer, 5, "1"); if(is_mute_l != 1) tinymix_set_value(mixer, 6, "1"); } mixer_close(mixer); return 0; } int audio_get_play_mute(void) { struct mixer *mixer; int is_mute_r = -1, is_mute_l = -1; mixer = mixer_open(0); if (!mixer) { fprintf(stderr, "Failed to open mixer\n"); return -1; } tinymix_get_value(mixer, 5, &is_mute_r); tinymix_get_value(mixer, 6, &is_mute_l); mixer_close(mixer); if(is_mute_r == 0 && is_mute_l == 0) { return 1; } if(is_mute_r == 1 || is_mute_l == 1) { return 0; } return -1; }