| /* AudioUsbALSA.cpp |
| Copyright (c) 2012, The Linux Foundation. All rights reserved. |
| |
| 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 Linux Foundation 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 "AS IS" AND ANY EXPRESS OR IMPLIED |
| WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT |
| ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS |
| 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.*/ |
| |
| #define LOG_TAG "AudioUsbALSA" |
| #define LOG_NDEBUG 0 |
| #define LOG_NDDEBUG 0 |
| #include <utils/Log.h> |
| #include <utils/String8.h> |
| |
| #include <cutils/properties.h> |
| #include <media/AudioRecord.h> |
| #include <hardware_legacy/power.h> |
| #include <sys/poll.h> |
| #include <sys/ioctl.h> |
| #include <fcntl.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <errno.h> |
| #include <jni.h> |
| #include <stdio.h> |
| #include <sys/eventfd.h> |
| |
| |
| #include "AudioUsbALSA.h" |
| struct pollfd pfdProxyPlayback[2]; |
| struct pollfd pfdUsbPlayback[2]; |
| struct pollfd pfdProxyRecording[1]; |
| struct pollfd pfdUsbRecording[1]; |
| |
| #define USB_PERIOD_SIZE 2048 |
| #define PROXY_PERIOD_SIZE 3072 |
| |
| namespace android_audio_legacy |
| { |
| AudioUsbALSA::AudioUsbALSA() |
| { |
| mproxypfdPlayback = -1; |
| musbpfdPlayback = -1; |
| mkillPlayBackThread = false; |
| mkillRecordingThread = false; |
| } |
| |
| AudioUsbALSA::~AudioUsbALSA() |
| { |
| mkillPlayBackThread = true; |
| mkillRecordingThread = true; |
| } |
| |
| |
| int AudioUsbALSA::getnumOfRates(char *ratesStr){ |
| int i, size = 0; |
| char *nextSRString, *temp_ptr; |
| nextSRString = strtok_r(ratesStr, " ,", &temp_ptr); |
| if (nextSRString == NULL) { |
| ALOGE("ERROR: getnumOfRates: could not find rates string"); |
| return NULL; |
| } |
| for (i = 1; nextSRString != NULL; i++) { |
| size ++; |
| nextSRString = strtok_r(NULL, " ,.-", &temp_ptr); |
| } |
| return size; |
| } |
| |
| |
| status_t AudioUsbALSA::getCap(char * type, int &channels, int &sampleRate) |
| { |
| ALOGD("getCap for %s",type); |
| long unsigned fileSize; |
| FILE *fp; |
| char *buffer; |
| int err = 1; |
| int size = 0; |
| int fd, i, lchannelsPlayback; |
| char *read_buf, *str_start, *channel_start, *ratesStr, *ratesStrForVal, |
| *ratesStrStart, *chString, *nextSRStr, *test, *nextSRString, *temp_ptr; |
| struct stat st; |
| memset(&st, 0x0, sizeof(struct stat)); |
| sampleRate = 0; |
| fd = open(PATH, O_RDONLY); |
| if (fd <0) { |
| ALOGE("ERROR: failed to open config file %s error: %d\n", PATH, errno); |
| close(fd); |
| return UNKNOWN_ERROR; |
| } |
| |
| if (fstat(fd, &st) < 0) { |
| ALOGE("ERROR: failed to stat %s error %d\n", PATH, errno); |
| close(fd); |
| return UNKNOWN_ERROR; |
| } |
| |
| fileSize = st.st_size; |
| |
| read_buf = (char *)malloc(BUFFSIZE); |
| memset(read_buf, 0x0, BUFFSIZE); |
| err = read(fd, read_buf, BUFFSIZE); |
| str_start = strstr(read_buf, type); |
| if (str_start == NULL) { |
| ALOGE("ERROR:%s section not found in usb config file", type); |
| close(fd); |
| free(read_buf); |
| return UNKNOWN_ERROR; |
| } |
| |
| channel_start = strstr(str_start, "Channels:"); |
| if (channel_start == NULL) { |
| ALOGE("ERROR: Could not find Channels information"); |
| close(fd); |
| free(read_buf); |
| return UNKNOWN_ERROR; |
| } |
| channel_start = strstr(channel_start, " "); |
| if (channel_start == NULL) { |
| ALOGE("ERROR: Channel section not found in usb config file"); |
| close(fd); |
| free(read_buf); |
| return UNKNOWN_ERROR; |
| } |
| |
| lchannelsPlayback = atoi(channel_start); |
| if (lchannelsPlayback == 1) { |
| channels = 1; |
| } else { |
| channels = 2; |
| } |
| ALOGD("channels supported by device: %d", lchannelsPlayback); |
| ratesStrStart = strstr(str_start, "Rates:"); |
| if (ratesStrStart == NULL) { |
| ALOGE("ERROR: Cant find rates information"); |
| close(fd); |
| free(read_buf); |
| return UNKNOWN_ERROR; |
| } |
| |
| ratesStrStart = strstr(ratesStrStart, " "); |
| if (ratesStrStart == NULL) { |
| ALOGE("ERROR: Channel section not found in usb config file"); |
| close(fd); |
| free(read_buf); |
| return UNKNOWN_ERROR; |
| } |
| |
| //copy to ratesStr, current line. |
| char *target = strchr(ratesStrStart, '\n'); |
| if (target == NULL) { |
| ALOGE("ERROR: end of line not found"); |
| close(fd); |
| free(read_buf); |
| return UNKNOWN_ERROR; |
| } |
| size = target - ratesStrStart; |
| ratesStr = (char *)malloc(size + 1) ; |
| ratesStrForVal = (char *)malloc(size + 1) ; |
| memcpy(ratesStr, ratesStrStart, size); |
| memcpy(ratesStrForVal, ratesStrStart, size); |
| ratesStr[size] = '\0'; |
| ratesStrForVal[size] = '\0'; |
| |
| size = getnumOfRates(ratesStr); |
| if (!size) { |
| ALOGE("ERROR: Could not get rate size, returning"); |
| close(fd); |
| free(ratesStrForVal); |
| free(ratesStr); |
| free(read_buf); |
| return UNKNOWN_ERROR; |
| } |
| |
| //populate playback rates array |
| int ratesSupported[size]; |
| nextSRString = strtok_r(ratesStrForVal, " ,", &temp_ptr); |
| if (nextSRString == NULL) { |
| ALOGE("ERROR: Could not get first rate val"); |
| close(fd); |
| free(ratesStrForVal); |
| free(ratesStr); |
| free(read_buf); |
| return UNKNOWN_ERROR; |
| } |
| |
| ratesSupported[0] = atoi(nextSRString); |
| for (i = 1; i<size; i++) { |
| nextSRString = strtok_r(NULL, " ,.-", &temp_ptr); |
| ratesSupported[i] = atoi(nextSRString); |
| ALOGV("ratesSupported[%d] for playback: %d",i, ratesSupported[i]); |
| } |
| |
| for (i = 0; i<=size; i++) { |
| if (ratesSupported[i] <= 48000) { |
| sampleRate = ratesSupported[i]; |
| break; |
| } |
| } |
| ALOGD("sampleRate: %d", sampleRate); |
| |
| close(fd); |
| free(ratesStrForVal); |
| free(ratesStr); |
| free(read_buf); |
| ratesStrForVal = NULL; |
| ratesStr = NULL; |
| read_buf = NULL; |
| return NO_ERROR; |
| } |
| |
| void AudioUsbALSA::exitPlaybackThread(uint64_t writeVal) |
| { |
| ALOGD("exitPlaybackThread, mproxypfdPlayback: %d", mproxypfdPlayback); |
| if (writeVal == SIGNAL_EVENT_KILLTHREAD) { |
| int err; |
| |
| err = closeDevice(mproxyPlaybackHandle); |
| if (err) { |
| ALOGE("Info: Could not close proxy %p", mproxyPlaybackHandle); |
| } |
| err = closeDevice(musbPlaybackHandle); |
| if (err) { |
| ALOGE("Info: Could not close USB device %p", musbPlaybackHandle); |
| } |
| } |
| if ((mproxypfdPlayback != -1) && (musbpfdPlayback != -1)) { |
| write(mproxypfdPlayback, &writeVal, sizeof(uint64_t)); |
| write(musbpfdPlayback, &writeVal, sizeof(uint64_t)); |
| mkillPlayBackThread = true; |
| pthread_join(mPlaybackUsb,NULL); |
| } |
| } |
| |
| void AudioUsbALSA::exitRecordingThread(uint64_t writeVal) |
| { |
| ALOGD("exitRecordingThread"); |
| if (writeVal == SIGNAL_EVENT_KILLTHREAD) { |
| int err; |
| |
| err = closeDevice(mproxyRecordingHandle); |
| if (err) { |
| ALOGE("Info: Could not close proxy for recording %p", mproxyRecordingHandle); |
| } |
| err = closeDevice(musbRecordingHandle); |
| if (err) { |
| ALOGE("Info: Could not close USB recording device %p", musbRecordingHandle); |
| } |
| } |
| mkillRecordingThread = true; |
| } |
| |
| void AudioUsbALSA::setkillUsbRecordingThread(bool val){ |
| ALOGD("setkillUsbRecordingThread"); |
| mkillRecordingThread = val; |
| } |
| |
| status_t AudioUsbALSA::setHardwareParams(pcm *txHandle, uint32_t sampleRate, uint32_t channels, int periodBytes) |
| { |
| ALOGD("setHardwareParams"); |
| struct snd_pcm_hw_params *params; |
| unsigned long bufferSize, reqBuffSize; |
| unsigned int periodTime, bufferTime; |
| unsigned int requestedRate = sampleRate; |
| int status = 0; |
| |
| params = (snd_pcm_hw_params*) calloc(1, sizeof(struct snd_pcm_hw_params)); |
| if (!params) { |
| return NO_INIT; |
| } |
| |
| param_init(params); |
| param_set_mask(params, SNDRV_PCM_HW_PARAM_ACCESS, |
| SNDRV_PCM_ACCESS_MMAP_INTERLEAVED); |
| param_set_mask(params, SNDRV_PCM_HW_PARAM_FORMAT, |
| SNDRV_PCM_FORMAT_S16_LE); |
| param_set_mask(params, SNDRV_PCM_HW_PARAM_SUBFORMAT, |
| SNDRV_PCM_SUBFORMAT_STD); |
| ALOGV("Setting period size:%d samplerate:%d, channels: %d",periodBytes,sampleRate, channels); |
| param_set_min(params, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, periodBytes); |
| param_set_int(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, 16); |
| param_set_int(params, SNDRV_PCM_HW_PARAM_FRAME_BITS, |
| channels - 1 ? 32 : 16); |
| param_set_int(params, SNDRV_PCM_HW_PARAM_CHANNELS, |
| channels); |
| param_set_int(params, SNDRV_PCM_HW_PARAM_RATE, sampleRate); |
| param_set_hw_refine(txHandle, params); |
| |
| if (param_set_hw_params(txHandle, params)) { |
| ALOGE("ERROR: cannot set hw params"); |
| return NO_INIT; |
| } |
| |
| param_dump(params); |
| |
| txHandle->period_size = pcm_period_size(params); |
| txHandle->buffer_size = pcm_buffer_size(params); |
| txHandle->period_cnt = txHandle->buffer_size/txHandle->period_size; |
| |
| ALOGD("setHardwareParams: buffer_size %d, period_size %d, period_cnt %d", |
| txHandle->buffer_size, txHandle->period_size, |
| txHandle->period_cnt); |
| |
| return NO_ERROR; |
| } |
| |
| status_t AudioUsbALSA::setSoftwareParams(pcm *pcm, bool playback) |
| { |
| ALOGD("setSoftwareParams"); |
| struct snd_pcm_sw_params* params; |
| |
| params = (snd_pcm_sw_params*) calloc(1, sizeof(struct snd_pcm_sw_params)); |
| if (!params) { |
| LOG_ALWAYS_FATAL("Failed to allocate ALSA software parameters!"); |
| return NO_INIT; |
| } |
| |
| params->tstamp_mode = SNDRV_PCM_TSTAMP_NONE; |
| params->period_step = 1; |
| |
| params->avail_min = (pcm->flags & PCM_MONO) ? pcm->period_size/2 : pcm->period_size/4; |
| |
| if (playback) { |
| params->start_threshold = (pcm->flags & PCM_MONO) ? pcm->period_size*8 : pcm->period_size*4; |
| params->xfer_align = (pcm->flags & PCM_MONO) ? pcm->period_size*8 : pcm->period_size*4; |
| } else { |
| params->start_threshold = (pcm->flags & PCM_MONO) ? pcm->period_size/2 : pcm->period_size/4; |
| params->xfer_align = (pcm->flags & PCM_MONO) ? pcm->period_size/2 : pcm->period_size/4; |
| } |
| params->stop_threshold = pcm->buffer_size; |
| |
| params->xfer_align = (pcm->flags & PCM_MONO) ? pcm->period_size/2 : pcm->period_size/4; |
| params->silence_size = 0; |
| params->silence_threshold = 0; |
| |
| if (param_set_sw_params(pcm, params)) { |
| ALOGE("ERROR: cannot set sw params"); |
| return NO_INIT; |
| } |
| |
| return NO_ERROR; |
| } |
| |
| status_t AudioUsbALSA::closeDevice(pcm *handle) |
| { |
| ALOGD("closeDevice handle %p", handle); |
| status_t err = NO_ERROR; |
| if (handle) { |
| err = pcm_close(handle); |
| if (err != NO_ERROR) { |
| ALOGE("INFO: closeDevice: pcm_close failed with err %d", err); |
| } |
| } |
| handle = NULL; |
| return err; |
| } |
| |
| void AudioUsbALSA::RecordingThreadEntry() { |
| ALOGD("Inside RecordingThreadEntry"); |
| int nfds = 1; |
| mtimeOutRecording = TIMEOUT_INFINITE; |
| int fd; |
| long frames; |
| static int start = 0; |
| struct snd_xferi x; |
| int filed; |
| unsigned avail, bufsize; |
| int bytes_written; |
| uint32_t sampleRate; |
| uint32_t channels; |
| u_int8_t *srcUsb_addr = NULL; |
| u_int8_t *dstProxy_addr = NULL; |
| int err; |
| const char *fn = "/data/RecordPcm.pcm"; |
| filed = open(fn, O_WRONLY | O_CREAT | O_TRUNC | O_APPEND, 0664); |
| |
| err = getCap((char *)"Capture:", mchannelsCapture, msampleRateCapture); |
| if (err) { |
| ALOGE("ERROR: Could not get capture capabilities from usb device"); |
| return; |
| } |
| int channelFlag = PCM_MONO; |
| if (mchannelsCapture >= 2) { |
| channelFlag = PCM_STEREO; |
| } |
| |
| musbRecordingHandle = configureDevice(PCM_IN|channelFlag|PCM_MMAP, (char *)"hw:1,0", |
| msampleRateCapture, mchannelsCapture,768,false); |
| if (!musbRecordingHandle) { |
| ALOGE("ERROR: Could not configure USB device for recording"); |
| return; |
| } else { |
| ALOGD("USB device Configured for recording"); |
| } |
| |
| pfdUsbRecording[0].fd = musbRecordingHandle->fd; //DEBUG |
| pfdUsbRecording[0].events = POLLIN; |
| |
| mproxyRecordingHandle = configureDevice(PCM_OUT|channelFlag|PCM_MMAP, (char *)"hw:0,7", |
| msampleRateCapture, mchannelsCapture,768,false); |
| if (!mproxyRecordingHandle) { |
| ALOGE("ERROR: Could not configure Proxy for recording"); |
| closeDevice(musbRecordingHandle); |
| return; |
| } else { |
| ALOGD("Proxy Configured for recording"); |
| } |
| |
| bufsize = musbRecordingHandle->period_size; |
| pfdProxyRecording[0].fd = mproxyRecordingHandle->fd; |
| pfdProxyRecording[0].events = POLLOUT; |
| frames = (musbRecordingHandle->flags & PCM_MONO) ? (bufsize / 2) : (bufsize / 4); |
| x.frames = (musbRecordingHandle->flags & PCM_MONO) ? (bufsize / 2) : (bufsize / 4); |
| |
| /***********************keep reading from usb and writing to proxy******************************************/ |
| while (mkillRecordingThread != true) { |
| if (!musbRecordingHandle->running) { |
| if (pcm_prepare(musbRecordingHandle)) { |
| ALOGE("ERROR: pcm_prepare failed for usb device for recording"); |
| mkillRecordingThread = true; |
| break;; |
| } |
| } |
| if (!mproxyRecordingHandle->running) { |
| if (pcm_prepare(mproxyRecordingHandle)) { |
| ALOGE("ERROR: pcm_prepare failed for proxy device for recording"); |
| mkillRecordingThread = true; |
| break;; |
| } |
| } |
| |
| /********** USB syncing before write **************/ |
| if (!musbRecordingHandle->start && !mkillRecordingThread) { |
| err = startDevice(musbRecordingHandle, &mkillRecordingThread); |
| if (err == EPIPE) { |
| continue; |
| } else if (err != NO_ERROR) { |
| mkillRecordingThread = true; |
| break; |
| } |
| } |
| for (;;) { |
| if (!musbRecordingHandle->running) { |
| if (pcm_prepare(musbRecordingHandle)) { |
| ALOGE("ERROR: pcm_prepare failed for proxy device for recording"); |
| mkillRecordingThread = true; |
| break; |
| } |
| } |
| /* Sync the current Application pointer from the kernel */ |
| musbRecordingHandle->sync_ptr->flags = SNDRV_PCM_SYNC_PTR_APPL | |
| SNDRV_PCM_SYNC_PTR_AVAIL_MIN; |
| |
| err = syncPtr(musbRecordingHandle, &mkillRecordingThread); |
| if (err == EPIPE) { |
| continue; |
| } else if (err != NO_ERROR) { |
| break; |
| } |
| |
| avail = pcm_avail(musbRecordingHandle); |
| if (avail < musbRecordingHandle->sw_p->avail_min) { |
| poll(pfdUsbRecording, nfds, TIMEOUT_INFINITE); |
| continue; |
| } else { |
| break; |
| } |
| } |
| if (mkillRecordingThread) { |
| break; |
| } |
| if (x.frames > avail) |
| frames = avail; |
| |
| srcUsb_addr = dst_address(musbRecordingHandle); |
| /**********End USB syncing before write**************/ |
| |
| /*************Proxy syncing before write ******************/ |
| |
| for (;;) { |
| if (!mproxyRecordingHandle->running) { |
| if (pcm_prepare(mproxyRecordingHandle)) { |
| ALOGE("ERROR: pcm_prepare failed for proxy device for recording"); |
| mkillRecordingThread = true; |
| break; |
| } |
| } |
| mproxyRecordingHandle->sync_ptr->flags = SNDRV_PCM_SYNC_PTR_APPL | |
| SNDRV_PCM_SYNC_PTR_AVAIL_MIN; |
| |
| err = syncPtr(mproxyRecordingHandle, &mkillRecordingThread); |
| if (err == EPIPE) { |
| continue; |
| } else if (err != NO_ERROR) { |
| break; |
| } |
| avail = pcm_avail(mproxyRecordingHandle); |
| if (avail < mproxyRecordingHandle->sw_p->avail_min) { |
| poll(pfdProxyRecording, nfds, TIMEOUT_INFINITE); |
| continue; |
| } else { |
| break; |
| } |
| } |
| if (mkillRecordingThread) { |
| break; |
| } |
| |
| dstProxy_addr = dst_address(mproxyRecordingHandle); |
| memset(dstProxy_addr, 0x0, bufsize); |
| |
| /**************End Proxy syncing before write *************/ |
| |
| memcpy(dstProxy_addr, srcUsb_addr, bufsize ); |
| |
| /************* sync up after write -- USB *********************/ |
| musbRecordingHandle->sync_ptr->c.control.appl_ptr += frames; |
| musbRecordingHandle->sync_ptr->flags = 0; |
| err = syncPtr(musbRecordingHandle, &mkillRecordingThread); |
| if (err == EPIPE) { |
| continue; |
| } else if (err != NO_ERROR) { |
| break; |
| } |
| |
| /************* end sync up after write -- USB *********************/ |
| |
| /**************** sync up after write -- Proxy ************************/ |
| mproxyRecordingHandle->sync_ptr->c.control.appl_ptr += frames; |
| mproxyRecordingHandle->sync_ptr->flags = 0; |
| |
| err = syncPtr(mproxyRecordingHandle, &mkillRecordingThread); |
| if (err == EPIPE) { |
| continue; |
| } else if (err != NO_ERROR) { |
| break; |
| } |
| |
| bytes_written = mproxyRecordingHandle->sync_ptr->c.control.appl_ptr - mproxyRecordingHandle->sync_ptr->s.status.hw_ptr; |
| if ((bytes_written >= mproxyRecordingHandle->sw_p->start_threshold) && (!mproxyRecordingHandle->start)) { |
| if (!mkillPlayBackThread) { |
| err = startDevice(mproxyRecordingHandle, &mkillRecordingThread); |
| if (err == EPIPE) { |
| continue; |
| } else if (err != NO_ERROR) { |
| mkillRecordingThread = true; |
| break; |
| } |
| } |
| } |
| } |
| /*************** End sync up after write -- Proxy *********************/ |
| if (mkillRecordingThread) { |
| closeDevice(mproxyRecordingHandle); |
| closeDevice(musbRecordingHandle); |
| } |
| ALOGD("Exiting USB Recording thread"); |
| } |
| |
| void *AudioUsbALSA::PlaybackThreadWrapper(void *me) { |
| static_cast<AudioUsbALSA *>(me)->PlaybackThreadEntry(); |
| return NULL; |
| } |
| |
| void *AudioUsbALSA::RecordingThreadWrapper(void *me) { |
| static_cast<AudioUsbALSA *>(me)->RecordingThreadEntry(); |
| return NULL; |
| } |
| |
| struct pcm * AudioUsbALSA::configureDevice(unsigned flags, char* hw, int sampleRate, int channelCount, int periodSize, bool playback){ |
| int err = NO_ERROR; |
| struct pcm * handle = NULL; |
| handle = pcm_open(flags, hw); |
| if (!handle || handle->fd < 0) { |
| ALOGE("ERROR: pcm_open failed"); |
| return NULL; |
| } |
| |
| if (!pcm_ready(handle)) { |
| ALOGE("ERROR: pcm_ready failed"); |
| closeDevice(handle); |
| return NULL; |
| } |
| |
| ALOGD("Setting hardware params: sampleRate:%d, channels: %d",sampleRate, channelCount); |
| err = setHardwareParams(handle, sampleRate, channelCount,periodSize); |
| if (err != NO_ERROR) { |
| ALOGE("ERROR: setHardwareParams failed"); |
| closeDevice(handle); |
| return NULL; |
| } |
| |
| err = setSoftwareParams(handle, playback); |
| if (err != NO_ERROR) { |
| ALOGE("ERROR: setSoftwareParams failed"); |
| closeDevice(handle); |
| return NULL; |
| } |
| |
| err = mmap_buffer(handle); |
| if (err) { |
| ALOGE("ERROR: mmap_buffer failed"); |
| closeDevice(handle); |
| return NULL; |
| } |
| |
| err = pcm_prepare(handle); |
| if (err) { |
| ALOGE("ERROR: pcm_prepare failed"); |
| closeDevice(handle); |
| return NULL; |
| } |
| |
| return handle; |
| } |
| |
| status_t AudioUsbALSA::startDevice(pcm *handle, bool *killThread) { |
| int err = NO_ERROR;; |
| if (ioctl(handle->fd, SNDRV_PCM_IOCTL_START)) { |
| err = -errno; |
| if (errno == EPIPE) { |
| ALOGE("ERROR: SNDRV_PCM_IOCTL_START returned EPIPE for usb recording case"); |
| handle->underruns++; |
| handle->running = 0; |
| handle->start = 0; |
| return errno; |
| } else { |
| ALOGE("ERROR: SNDRV_PCM_IOCTL_START failed for usb recording case errno:%d", errno); |
| *killThread = true; |
| return errno; |
| } |
| } |
| handle->start = 1; |
| if (handle == musbRecordingHandle) { |
| ALOGD("Usb Driver started for recording"); |
| } else if (handle == mproxyRecordingHandle) { |
| ALOGD("Proxy Driver started for recording"); |
| } else if (handle == musbPlaybackHandle) { |
| ALOGD("Usb Driver started for playback"); |
| } else if (handle == mproxyPlaybackHandle) { |
| ALOGD("proxy Driver started for playback"); |
| } |
| return NO_ERROR; |
| } |
| |
| status_t AudioUsbALSA::syncPtr(struct pcm *handle, bool *killThread) { |
| int err; |
| err = sync_ptr(handle); |
| if (err == EPIPE) { |
| ALOGE("ERROR: Failed in sync_ptr \n"); |
| handle->running = 0; |
| handle->underruns++; |
| handle->start = 0; |
| } else if (err == ENODEV) { |
| ALOGE("Info: Device not available"); |
| } else if (err != NO_ERROR) { |
| ALOGE("ERROR: Sync ptr returned %d", err); |
| *killThread = true; |
| } |
| return err; |
| } |
| |
| void AudioUsbALSA::pollForProxyData(){ |
| int err_poll = poll(pfdProxyPlayback, mnfdsPlayback, mtimeOut); |
| if (err_poll == 0 ) { |
| ALOGD("POLL timedout"); |
| mkillPlayBackThread = true; |
| pfdProxyPlayback[0].revents = 0; |
| pfdProxyPlayback[1].revents = 0; |
| return; |
| } |
| |
| if (pfdProxyPlayback[1].revents & POLLIN) { |
| ALOGD("Signalled from HAL about timeout"); |
| uint64_t u; |
| read(mproxypfdPlayback, &u, sizeof(uint64_t)); |
| pfdProxyPlayback[1].revents = 0; |
| if (u == SIGNAL_EVENT_KILLTHREAD) { |
| ALOGD("kill thread event"); |
| mkillPlayBackThread = true; |
| pfdProxyPlayback[0].revents = 0; |
| pfdProxyPlayback[1].revents = 0; |
| return; |
| } else if (u == SIGNAL_EVENT_TIMEOUT) { |
| ALOGD("Setting timeout for 3 sec"); |
| mtimeOut = POLL_TIMEOUT; |
| } |
| } else if (pfdProxyPlayback[1].revents & POLLERR || pfdProxyPlayback[1].revents & POLLHUP || |
| pfdProxyPlayback[1].revents & POLLNVAL) { |
| ALOGE("Info: proxy throwing error from location 1"); |
| mkillPlayBackThread = true; |
| pfdProxyPlayback[0].revents = 0; |
| pfdProxyPlayback[1].revents = 0; |
| return; |
| } |
| |
| if (pfdProxyPlayback[0].revents & POLLERR || pfdProxyPlayback[0].revents & POLLHUP || |
| pfdProxyPlayback[0].revents & POLLNVAL) { |
| ALOGE("Info: proxy throwing error"); |
| mkillPlayBackThread = true; |
| pfdProxyPlayback[0].revents = 0; |
| pfdProxyPlayback[1].revents = 0; |
| } |
| } |
| |
| void AudioUsbALSA::pollForUsbData(){ |
| int err_poll = poll(pfdUsbPlayback, mnfdsPlayback, mtimeOut); |
| if (err_poll == 0 ) { |
| ALOGD("POLL timedout"); |
| mkillPlayBackThread = true; |
| pfdUsbPlayback[0].revents = 0; |
| pfdUsbPlayback[1].revents = 0; |
| return; |
| } |
| |
| if (pfdUsbPlayback[1].revents & POLLIN) { |
| ALOGD("Info: Signalled from HAL about an event"); |
| uint64_t u; |
| read(musbpfdPlayback, &u, sizeof(uint64_t)); |
| pfdUsbPlayback[0].revents = 0; |
| pfdUsbPlayback[1].revents = 0; |
| if (u == SIGNAL_EVENT_KILLTHREAD) { |
| ALOGD("kill thread"); |
| mkillPlayBackThread = true; |
| return; |
| } else if (u == SIGNAL_EVENT_TIMEOUT) { |
| ALOGD("Setting timeout for 3 sec"); |
| mtimeOut = POLL_TIMEOUT; |
| } |
| } else if (pfdUsbPlayback[1].revents & POLLERR || pfdUsbPlayback[1].revents & POLLHUP || |
| pfdUsbPlayback[1].revents & POLLNVAL) { |
| ALOGE("Info: usb throwing error from location 1"); |
| mkillPlayBackThread = true; |
| pfdUsbPlayback[0].revents = 0; |
| pfdUsbPlayback[1].revents = 0; |
| return; |
| } |
| |
| if (pfdUsbPlayback[0].revents & POLLERR || pfdProxyPlayback[0].revents & POLLHUP || |
| pfdUsbPlayback[0].revents & POLLNVAL) { |
| ALOGE("Info: usb throwing error"); |
| mkillPlayBackThread = true; |
| pfdUsbPlayback[0].revents = 0; |
| return; |
| } |
| } |
| |
| void AudioUsbALSA::PlaybackThreadEntry() { |
| ALOGD("PlaybackThreadEntry"); |
| mnfdsPlayback = 2; |
| mtimeOut = TIMEOUT_INFINITE; |
| long frames; |
| static int fd; |
| struct snd_xferi x; |
| int bytes_written; |
| unsigned avail, xfer, bufsize; |
| unsigned proxyPeriod, usbPeriod; |
| uint32_t sampleRate; |
| uint32_t channels; |
| unsigned int tmp; |
| int numOfBytesWritten; |
| int err; |
| int filed; |
| const char *fn = "/data/test.pcm"; |
| mdstUsb_addr = NULL; |
| msrcProxy_addr = NULL; |
| |
| int proxySizeRemaining = 0; |
| int usbSizeFilled = 0; |
| |
| pid_t tid = gettid(); |
| androidSetThreadPriority(tid, ANDROID_PRIORITY_URGENT_AUDIO); |
| |
| err = getCap((char *)"Playback:", mchannelsPlayback, msampleRatePlayback); |
| if (err) { |
| ALOGE("ERROR: Could not get playback capabilities from usb device"); |
| return; |
| } |
| |
| musbPlaybackHandle = configureDevice(PCM_OUT|PCM_STEREO|PCM_MMAP, (char *)"hw:1,0", |
| msampleRatePlayback, mchannelsPlayback, USB_PERIOD_SIZE, true); |
| if (!musbPlaybackHandle) { |
| ALOGE("ERROR: configureUsbDevice failed, returning"); |
| closeDevice(musbPlaybackHandle); |
| return; |
| } else { |
| ALOGD("USB Configured for playback"); |
| } |
| |
| if (!mkillPlayBackThread) { |
| pfdUsbPlayback[0].fd = musbPlaybackHandle->timer_fd; |
| pfdUsbPlayback[0].events = POLLIN; |
| musbpfdPlayback = eventfd(0,0); |
| pfdUsbPlayback[1].fd = musbpfdPlayback; |
| pfdUsbPlayback[1].events = (POLLIN | POLLOUT | POLLERR | POLLNVAL | POLLHUP); |
| } |
| |
| mproxyPlaybackHandle = configureDevice(PCM_IN|PCM_STEREO|PCM_MMAP, (char *)"hw:0,8", |
| msampleRatePlayback, mchannelsPlayback, PROXY_PERIOD_SIZE, false); |
| if (!mproxyPlaybackHandle) { |
| ALOGE("ERROR: Could not configure Proxy, returning"); |
| closeDevice(musbPlaybackHandle); |
| return; |
| } else { |
| ALOGD("Proxy Configured for playback"); |
| } |
| |
| proxyPeriod = mproxyPlaybackHandle->period_size; |
| usbPeriod = musbPlaybackHandle->period_size; |
| |
| if (!mkillPlayBackThread) { |
| pfdProxyPlayback[0].fd = mproxyPlaybackHandle->fd; |
| pfdProxyPlayback[0].events = (POLLIN); // | POLLERR | POLLNVAL); |
| mproxypfdPlayback = eventfd(0,0); |
| pfdProxyPlayback[1].fd = mproxypfdPlayback; |
| pfdProxyPlayback[1].events = (POLLIN | POLLOUT| POLLERR | POLLNVAL); |
| } |
| |
| frames = (mproxyPlaybackHandle->flags & PCM_MONO) ? (proxyPeriod / 2) : (proxyPeriod / 4); |
| x.frames = (mproxyPlaybackHandle->flags & PCM_MONO) ? (proxyPeriod / 2) : (proxyPeriod / 4); |
| int usbframes = (musbPlaybackHandle->flags & PCM_MONO) ? (usbPeriod / 2) : (usbPeriod / 4); |
| |
| u_int8_t *proxybuf = ( u_int8_t *) malloc(PROXY_PERIOD_SIZE); |
| u_int8_t *usbbuf = ( u_int8_t *) malloc(USB_PERIOD_SIZE); |
| memset(proxybuf, 0x0, PROXY_PERIOD_SIZE); |
| memset(usbbuf, 0x0, USB_PERIOD_SIZE); |
| |
| |
| /***********************keep reading from proxy and writing to USB******************************************/ |
| while (mkillPlayBackThread != true) { |
| if (!mproxyPlaybackHandle->running) { |
| if (pcm_prepare(mproxyPlaybackHandle)) { |
| ALOGE("ERROR: pcm_prepare failed for proxy"); |
| mkillPlayBackThread = true; |
| break; |
| } |
| } |
| if (!musbPlaybackHandle->running) { |
| if (pcm_prepare(musbPlaybackHandle)) { |
| ALOGE("ERROR: pcm_prepare failed for usb"); |
| mkillPlayBackThread = true; |
| break; |
| } |
| } |
| |
| /********** Proxy syncing before write **************/ |
| if (!mkillPlayBackThread && (!mproxyPlaybackHandle->start)) { |
| err = startDevice(mproxyPlaybackHandle, &mkillPlayBackThread); |
| if (err == EPIPE) { |
| continue; |
| } else if (err != NO_ERROR) { |
| mkillPlayBackThread = true; |
| break; |
| } |
| } |
| if (proxySizeRemaining == 0) { |
| for (;;) { |
| if (!mproxyPlaybackHandle->running) { |
| if (pcm_prepare(mproxyPlaybackHandle)) { |
| ALOGE("ERROR: pcm_prepare failed for proxy"); |
| mkillPlayBackThread = true; |
| break; |
| } |
| } |
| /* Sync the current Application pointer from the kernel */ |
| mproxyPlaybackHandle->sync_ptr->flags = SNDRV_PCM_SYNC_PTR_APPL | |
| SNDRV_PCM_SYNC_PTR_AVAIL_MIN; |
| |
| if (mtimeOut == TIMEOUT_INFINITE && !mkillPlayBackThread) { |
| err = syncPtr(mproxyPlaybackHandle, &mkillPlayBackThread); |
| if (err == EPIPE) { |
| continue; |
| } else if (err != NO_ERROR) { |
| break; |
| } |
| avail = pcm_avail(mproxyPlaybackHandle); |
| } |
| if (avail < mproxyPlaybackHandle->sw_p->avail_min && !mkillPlayBackThread) { |
| pollForProxyData(); |
| //if polling returned some error |
| if (!mkillPlayBackThread) { |
| continue; |
| } else { |
| break; |
| } |
| } else { //Got some data or mkillPlayBackThread is true |
| break; |
| } |
| } |
| if (mkillPlayBackThread) { |
| break; |
| } |
| |
| if (x.frames > avail) |
| frames = avail; |
| |
| if (!mkillPlayBackThread) { |
| msrcProxy_addr = dst_address(mproxyPlaybackHandle); |
| memcpy(proxybuf, msrcProxy_addr, proxyPeriod ); |
| |
| x.frames -= frames; |
| mproxyPlaybackHandle->sync_ptr->c.control.appl_ptr += frames; |
| mproxyPlaybackHandle->sync_ptr->flags = 0; |
| proxySizeRemaining = proxyPeriod; |
| } |
| |
| if (!mkillPlayBackThread) { |
| err = syncPtr(mproxyPlaybackHandle, &mkillPlayBackThread); |
| if (err == EPIPE) { |
| continue; |
| } else if (err != NO_ERROR) { |
| break; |
| } |
| } |
| } |
| //ALOGE("usbSizeFilled %d, proxySizeRemaining %d ",usbSizeFilled,proxySizeRemaining); |
| if (usbPeriod - usbSizeFilled <= proxySizeRemaining) { |
| memcpy(usbbuf + usbSizeFilled, proxybuf + proxyPeriod - proxySizeRemaining, usbPeriod - usbSizeFilled); |
| proxySizeRemaining -= (usbPeriod - usbSizeFilled); |
| usbSizeFilled = usbPeriod; |
| } |
| else { |
| memcpy(usbbuf + usbSizeFilled, proxybuf + proxyPeriod - proxySizeRemaining,proxySizeRemaining); |
| usbSizeFilled += proxySizeRemaining; |
| proxySizeRemaining = 0; |
| } |
| |
| if (usbSizeFilled == usbPeriod) { |
| for (;;) { |
| if (!musbPlaybackHandle->running) { |
| if (pcm_prepare(musbPlaybackHandle)) { |
| ALOGE("ERROR: pcm_prepare failed for usb"); |
| mkillPlayBackThread = true; |
| break; |
| } |
| } |
| /*************USB syncing before write ******************/ |
| musbPlaybackHandle->sync_ptr->flags = SNDRV_PCM_SYNC_PTR_APPL | |
| SNDRV_PCM_SYNC_PTR_AVAIL_MIN; |
| if (mtimeOut == TIMEOUT_INFINITE && !mkillPlayBackThread) { |
| err = syncPtr(musbPlaybackHandle, &mkillPlayBackThread); |
| if (err == EPIPE) { |
| continue; |
| } else if (err != NO_ERROR) { |
| break; |
| } |
| avail = pcm_avail(musbPlaybackHandle); |
| //ALOGV("Avail USB is: %d", avail); |
| } |
| |
| if (avail < musbPlaybackHandle->sw_p->avail_min && !mkillPlayBackThread) { |
| pollForUsbData(); |
| if (!mkillPlayBackThread) { |
| continue; |
| } else { |
| break; |
| } |
| } else { |
| break; |
| } |
| } |
| if (mkillPlayBackThread) { |
| break; |
| } |
| |
| if (!mkillPlayBackThread) { |
| mdstUsb_addr = dst_address(musbPlaybackHandle); |
| |
| /**************End USB syncing before write *************/ |
| |
| memcpy(mdstUsb_addr, usbbuf, usbPeriod ); |
| usbSizeFilled = 0; |
| memset(usbbuf, 0x0, usbPeriod); |
| } |
| |
| /**************** sync up after write -- USB ************************/ |
| musbPlaybackHandle->sync_ptr->c.control.appl_ptr += usbframes; |
| musbPlaybackHandle->sync_ptr->flags = 0; |
| if (!mkillPlayBackThread) { |
| err = syncPtr(musbPlaybackHandle, &mkillPlayBackThread); |
| if (err == EPIPE) { |
| continue; |
| } else if (err != NO_ERROR) { |
| break; |
| } |
| } |
| |
| bytes_written = musbPlaybackHandle->sync_ptr->c.control.appl_ptr - musbPlaybackHandle->sync_ptr->s.status.hw_ptr; |
| ALOGE("Appl ptr %d , hw_ptr %d, difference %d",musbPlaybackHandle->sync_ptr->c.control.appl_ptr, musbPlaybackHandle->sync_ptr->s.status.hw_ptr, bytes_written); |
| |
| /* |
| Following is the check to prevent USB from going to bad state. |
| This happens in case of an underrun where there is not enough |
| data from the proxy |
| */ |
| if (bytes_written <= usbPeriod && musbPlaybackHandle->start) { |
| ioctl(musbPlaybackHandle->fd, SNDRV_PCM_IOCTL_PAUSE,1); |
| pcm_prepare(musbPlaybackHandle); |
| musbPlaybackHandle->start = false; |
| continue; |
| } |
| if ((bytes_written >= musbPlaybackHandle->sw_p->start_threshold) && (!musbPlaybackHandle->start)) { |
| if (!mkillPlayBackThread) { |
| err = startDevice(musbPlaybackHandle, &mkillPlayBackThread); |
| if (err == EPIPE) { |
| continue; |
| } else if (err != NO_ERROR) { |
| mkillPlayBackThread = true; |
| break; |
| } |
| } |
| } |
| /*************** End sync up after write -- USB *********************/ |
| } |
| } |
| if (mkillPlayBackThread) { |
| if (proxybuf) |
| free(proxybuf); |
| if (usbbuf) |
| free(usbbuf); |
| mproxypfdPlayback = -1; |
| musbpfdPlayback = -1; |
| closeDevice(mproxyPlaybackHandle); |
| closeDevice(musbPlaybackHandle); |
| } |
| ALOGD("Exiting USB Playback Thread"); |
| } |
| |
| void AudioUsbALSA::startPlayback() |
| { |
| mkillPlayBackThread = false; |
| ALOGD("Creating USB Playback Thread"); |
| pthread_create(&mPlaybackUsb, NULL, PlaybackThreadWrapper, this); |
| } |
| |
| void AudioUsbALSA::startRecording() |
| { |
| //create Thread |
| mkillRecordingThread = false; |
| ALOGV("Creating USB recording Thread"); |
| pthread_create(&mRecordingUsb, NULL, RecordingThreadWrapper, this); |
| } |
| } |