text: /*
  ** 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;
  }
  