/****************************************************************************
 * audio/pcm_decode.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you 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.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <sys/param.h>
#include <sys/types.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>

#include <nuttx/kmalloc.h>
#include <nuttx/audio/audio.h>
#include <nuttx/audio/pcm.h>

#if defined(CONFIG_AUDIO) && defined(CONFIG_AUDIO_FORMAT_PCM)

/****************************************************************************
 * Private Types
 ****************************************************************************/

/* This structure describes the internal state of the PCM decoder */

struct pcm_decode_s
{
  /* This is is our our appearance to the outside world.  This *MUST* be the
   * first element of the structure so that we can freely cast between types
   * struct audio_lowerhalf and struct pcm_decode_s.
   */

  struct audio_lowerhalf_s export;

  /* These are our operations that intervene between the player application
   * and the lower level driver.  Unlike the ops in the struct
   * audio_lowerhalf_s, these are writeable because we need to customize a
   * few of the methods based upon what is supported by the lower level
   * driver.
   */

  struct audio_ops_s ops;

  /* This is the contained, low-level DAC-type device and will receive the
   * decoded PCM audio data.
   */

  FAR struct audio_lowerhalf_s *lower;

  /* Session returned from the lower level driver */

#ifdef CONFIG_AUDIO_MULTI_SESSION
  FAR void *session;
#endif

  /* These are values extracted from WAV file header */

  uint32_t samprate;               /* 8000, 44100, ... */
  uint32_t byterate;               /* samprate * nchannels * bpsamp / 8 */
  uint8_t  align;                  /* nchannels * bpsamp / 8 */
  uint8_t  bpsamp;                 /* Bits per sample: 8 bits = 8, 16 bits = 16 */
  uint8_t  nchannels;              /* Mono=1, Stereo=2 */
  bool     streaming;              /* Streaming PCM data chunk */

#ifndef CONFIG_AUDIO_EXCLUDE_FFORWARD
  /* Fast forward support */

  uint8_t  subsample;              /* Fast forward rate: See AUDIO_SUBSAMPLE_* defns */
  uint8_t  skip;                   /* Number of sample bytes to be skipped */
  uint8_t  npartial;               /* Size of the partially copied sample */
#endif
};

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/* Helper functions *********************************************************/

#ifdef CONFIG_DEBUG_AUDIO_INFO
static void pcm_dump(FAR const struct wav_header_s *wav);
#else
#  define pcm_dump(w)
#endif

#ifndef CONFIG_AUDIO_FORMAT_RAW
static inline bool pcm_validwav(FAR const struct wav_header_s *wav);
static ssize_t pcm_parsewav(FAR struct pcm_decode_s *priv, uint8_t *data,
                            apb_samp_t len);
#endif

#ifndef CONFIG_AUDIO_EXCLUDE_FFORWARD
static void pcm_subsample_configure(FAR struct pcm_decode_s *priv,
              uint8_t subsample);
static void pcm_subsample(FAR struct pcm_decode_s *priv,
              FAR struct ap_buffer_s *apb);
#endif

/* struct audio_lowerhalf_s methods *****************************************/

static int  pcm_getcaps(FAR struct audio_lowerhalf_s *dev, int type,
              FAR struct audio_caps_s *caps);

#ifdef CONFIG_AUDIO_MULTI_SESSION
static int  pcm_configure(FAR struct audio_lowerhalf_s *dev,
              FAR void *session, FAR const struct audio_caps_s *caps);
#else
static int  pcm_configure(FAR struct audio_lowerhalf_s *dev,
              FAR const struct audio_caps_s *caps);
#endif

static int  pcm_shutdown(FAR struct audio_lowerhalf_s *dev);

#ifdef CONFIG_AUDIO_MULTI_SESSION
static int  pcm_start(FAR struct audio_lowerhalf_s *dev, FAR void *session);
#else
static int  pcm_start(FAR struct audio_lowerhalf_s *dev);
#endif

#ifndef CONFIG_AUDIO_EXCLUDE_STOP
#ifdef CONFIG_AUDIO_MULTI_SESSION
static int  pcm_stop(FAR struct audio_lowerhalf_s *dev, FAR void *session);
#else
static int  pcm_stop(FAR struct audio_lowerhalf_s *dev);
#endif
#endif

#ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME
#ifdef CONFIG_AUDIO_MULTI_SESSION
static int  pcm_pause(FAR struct audio_lowerhalf_s *dev, FAR void *session);
#else
static int  pcm_pause(FAR struct audio_lowerhalf_s *dev);
#endif

#ifdef CONFIG_AUDIO_MULTI_SESSION
static int  pcm_resume(FAR struct audio_lowerhalf_s *dev, FAR void *session);
#else
static int  pcm_resume(FAR struct audio_lowerhalf_s *dev);
#endif
#endif

static int  pcm_allocbuffer(FAR struct audio_lowerhalf_s *dev,
              FAR struct audio_buf_desc_s *apb);
static int  pcm_freebuffer(FAR struct audio_lowerhalf_s *dev,
              FAR struct audio_buf_desc_s *apb);
static int  pcm_enqueuebuffer(FAR struct audio_lowerhalf_s *dev,
              FAR struct ap_buffer_s *apb);
static int  pcm_cancelbuffer(FAR struct audio_lowerhalf_s *dev,
              FAR struct ap_buffer_s *apb);
static int  pcm_ioctl(FAR struct audio_lowerhalf_s *dev,
              int cmd, unsigned long arg);

#ifdef CONFIG_AUDIO_MULTI_SESSION
static int  pcm_reserve(FAR struct audio_lowerhalf_s *dev,
                        FAR void **session);
#else
static int  pcm_reserve(FAR struct audio_lowerhalf_s *dev);
#endif

#ifdef CONFIG_AUDIO_MULTI_SESSION
static int  pcm_release(FAR struct audio_lowerhalf_s *dev,
                        FAR void *session);
#else
static int  pcm_release(FAR struct audio_lowerhalf_s *dev);
#endif

/* Audio callback */

#ifdef CONFIG_AUDIO_MULTI_SESSION
static void pcm_callback(FAR void *arg, uint16_t reason,
              FAR struct ap_buffer_s *apb, uint16_t status,
              FAR void *session);
#else
static void pcm_callback(FAR void *arg, uint16_t reason,
              FAR struct ap_buffer_s *apb, uint16_t status);
#endif

/****************************************************************************
 * Private Data
 ****************************************************************************/

/****************************************************************************
 * Public Data
 ****************************************************************************/

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: pcm_dump
 *
 * Description:
 *   Dump a WAV file header.
 *
 ****************************************************************************/

#ifdef CONFIG_DEBUG_AUDIO_INFO
static void pcm_dump(FAR const struct wav_header_s *wav)
{
  _info("Wave file header\n");
  _info("  Header Chunk:\n");
  _info("    Chunk ID:        0x%08" PRIx32 "\n", wav->hdr.chunkid);
  _info("    Chunk Size:      %" PRIu32 "\n",     wav->hdr.chunklen);
  _info("    Format:          0x%08" PRIx32 "\n", wav->hdr.format);
  _info("  Format Chunk:\n");
  _info("    Chunk ID:        0x%08" PRIx32 "\n", wav->fmt.chunkid);
  _info("    Chunk Size:      %" PRIu32 "\n",     wav->fmt.chunklen);
  _info("    Audio Format:    0x%04x\n",          wav->fmt.format);
  _info("    Num. Channels:   %d\n",              wav->fmt.nchannels);
  _info("    Sample Rate:     %" PRIu32 "\n",     wav->fmt.samprate);
  _info("    Byte Rate:       %" PRIu32 "\n",     wav->fmt.byterate);
  _info("    Block Align:     %d\n",              wav->fmt.align);
  _info("    Bits Per Sample: %d\n",              wav->fmt.bpsamp);
  _info("  Data Chunk:\n");
  _info("    Chunk ID:        0x%08" PRIx32 "\n", wav->data.chunkid);
  _info("    Chunk Size:      %" PRIu32 "\n",     wav->data.chunklen);
}
#endif

/****************************************************************************
 * Name: pcm_leuint16
 *
 * Description:
 *   Get a 16-bit value stored in little endian order.  Unaligned address is
 *   acceptable.
 *
 ****************************************************************************/

#ifndef CONFIG_AUDIO_FORMAT_RAW
static uint16_t pcm_leuint16(FAR const uint16_t *ptr)
{
  FAR const uint8_t *p = (FAR const uint8_t *)ptr;

  return ((p[0] <<  0) |
          (p[1] <<  8));
}

/****************************************************************************
 * Name: pcm_leuint32
 *
 * Description:
 *   Get a 32-bit value stored in little endian order.  Unaligned address is
 *   acceptable.
 *
 ****************************************************************************/

static uint32_t pcm_leuint32(FAR const uint32_t *ptr)
{
  FAR const uint8_t *p = (FAR const uint8_t *)ptr;

  return ((p[0] <<  0) |
          (p[1] <<  8) |
          (p[2] << 16) |
          (p[3] << 24));
}

/****************************************************************************
 * Name: pcm_validwav
 *
 * Description:
 *   Return true if this is a valid WAV file header
 *
 ****************************************************************************/

static inline bool pcm_validwav(FAR const struct wav_header_s *wav)
{
  return (wav->hdr.chunkid  == WAV_HDR_CHUNKID  &&
          wav->hdr.format   == WAV_HDR_FORMAT   &&
          wav->fmt.chunkid  == WAV_FMT_CHUNKID  &&
          wav->fmt.chunklen == WAV_FMT_CHUNKLEN &&
          wav->fmt.format   == WAV_FMT_FORMAT   &&
          wav->fmt.nchannels < 256              &&
          wav->fmt.align     < 256              &&
          wav->fmt.bpsamp    < 256);
}

/****************************************************************************
 * Name: pcm_parsewav
 *
 * Description:
 *   Parse and verify the WAV file header.  A WAV file is a particular
 *   packaging of an audio file; on PCM encoded WAV files are accepted by
 *   this driver.
 *
 ****************************************************************************/

static ssize_t pcm_parsewav(FAR struct pcm_decode_s *priv, uint8_t *data,
                            apb_samp_t len)
{
  FAR const struct wav_header_s *wav = (FAR const struct wav_header_s *)data;
  FAR const struct wav_datachunk_s *dchunk;
  struct wav_header_s localwav;
  size_t ret = sizeof(struct wav_header_s);

  if (len < sizeof(struct wav_header_s))
    {
      return -EINVAL;
    }

  /* Transfer the purported WAV file header into our stack storage,
   * correcting for endian issues as needed.
   */

  localwav.hdr.chunkid   = pcm_leuint32(&wav->hdr.chunkid);
  localwav.hdr.chunklen  = pcm_leuint32(&wav->hdr.chunklen);
  localwav.hdr.format    = pcm_leuint32(&wav->hdr.format);

  localwav.fmt.chunkid   = pcm_leuint32(&wav->fmt.chunkid);
  localwav.fmt.chunklen  = pcm_leuint32(&wav->fmt.chunklen);
  localwav.fmt.format    = pcm_leuint16(&wav->fmt.format);
  localwav.fmt.nchannels = pcm_leuint16(&wav->fmt.nchannels);
  localwav.fmt.samprate  = pcm_leuint32(&wav->fmt.samprate);
  localwav.fmt.byterate  = pcm_leuint32(&wav->fmt.byterate);
  localwav.fmt.align     = pcm_leuint16(&wav->fmt.align);
  localwav.fmt.bpsamp    = pcm_leuint16(&wav->fmt.bpsamp);

  /* Find the data chunk */

  dchunk = &wav->data;

  for (; ; )
    {
      /* NOTE: The data chunk is possible to be not word-aligned if extra
       * chunks exist before it.
       */

      localwav.data.chunkid  = pcm_leuint32(&dchunk->chunkid);
      localwav.data.chunklen = pcm_leuint32(&dchunk->chunklen);

      if (localwav.data.chunkid == WAV_DATA_CHUNKID)
        {
          break;
        }

      /* Not data chunk. Skip it. */

      ret += localwav.data.chunklen + 8;
      if (ret >= len)
        {
          /* Data chunk not found */

          return -EINVAL;
        }

      dchunk = (FAR const struct wav_datachunk_s *)
               ((uintptr_t)dchunk + localwav.data.chunklen + 8);
    }

  /* Dump the converted wave header information */

  pcm_dump(&localwav);

  /* Check if the file is a valid PCM WAV header */

  if (!pcm_validwav(&localwav))
    {
      return -EINVAL;
    }
  else
    {
      /* Yes... pick off the relevant format values and save then in the
       * device structure.
       */

      priv->samprate    = localwav.fmt.samprate;  /* 8000, 44100, ... */
      priv->byterate    = localwav.fmt.byterate;  /* samprate * nchannels * bpsamp / 8 */
      priv->align       = localwav.fmt.align;     /* nchannels * bpsamp / 8 */
      priv->bpsamp      = localwav.fmt.bpsamp;    /* Bits per sample: 8 bits = 8, 16 bits = 16 */
      priv->nchannels   = localwav.fmt.nchannels; /* Mono=1, Stereo=2 */

#ifndef CONFIG_AUDIO_EXCLUDE_FFORWARD
      /* We are going to subsample, there then are some restrictions on the
       * number of channels and sample sizes that we can handle.
       */

      if (priv->bpsamp != 8 && priv->bpsamp != 16)
        {
          auderr("ERROR: %d bits per sample are not supported in this "
                 "mode\n",
                 priv->bpsamp);
          return -EINVAL;
        }

      if (priv->nchannels != 1 && priv->nchannels != 2)
        {
          auderr("ERROR: %d channels are not supported in this mode\n",
                 priv->nchannels);
          return -EINVAL;
        }

      DEBUGASSERT(priv->align == priv->nchannels * priv->bpsamp / 8);
#endif
    }

  /* And return true if the file is a valid WAV header file */

  return ret;
}
#endif

/****************************************************************************
 * Name: pcm_subsample_configure
 *
 * Description:
 *   Configure to perform sub-sampling (or not) on the following audio
 *   buffers.
 *
 ****************************************************************************/

#ifndef CONFIG_AUDIO_EXCLUDE_FFORWARD
static void pcm_subsample_configure(FAR struct pcm_decode_s *priv,
                                    uint8_t subsample)
{
  audinfo("subsample: %d\n", subsample);

  /* Three possibilities:
   *
   * 1. We were playing normally and we have been requested to begin fast
   *    forwarding.
   */

  if (priv->subsample == AUDIO_SUBSAMPLE_NONE)
    {
      /* Ignore request to stop fast forwarding if we are already playing
       * normally.
       */

      if (subsample != AUDIO_SUBSAMPLE_NONE)
        {
          audinfo("Start sub-sampling\n");

          /* Save the current subsample setting. Subsampling will begin on
           * then next audio buffer that we receive.
           */

          priv->npartial  = 0;
          priv->skip      = 0;
          priv->subsample = subsample;
        }
    }

  /* 2. We were already fast forwarding and we have been asked to return to
   *    normal play.
   */

  else if (subsample == AUDIO_SUBSAMPLE_NONE)
    {
      audinfo("Stop sub-sampling\n");

      /* Indicate that we are in normal play mode.  This will take effect
       * when the next audio buffer is received.
       */

      priv->npartial  = 0;
      priv->skip      = 0;
      priv->subsample = AUDIO_SUBSAMPLE_NONE;
    }

  /* 3. Were already fast forwarding and we have been asked to change the
   *    sub-sampling rate.
   */

  else if (priv->subsample != subsample)
    {
      /* Just save the new subsample setting.  It will take effect on the
       * next audio buffer that we receive.
       */

       priv->subsample = subsample;
    }
}
#endif

/****************************************************************************
 * Name: pcm_subsample
 *
 * Description:
 *   Given a newly received audio buffer, perform sub-sampling in-place in
 *   the audio buffer.  Since the sub-sampled data will always be smaller
 *   than the original buffer, no additional buffering should be necessary.
 *
 ****************************************************************************/

#ifndef CONFIG_AUDIO_EXCLUDE_FFORWARD
static void pcm_subsample(FAR struct pcm_decode_s *priv,
                          FAR struct ap_buffer_s *apb)
{
  FAR const uint8_t *src;
  FAR uint8_t *dest;
  unsigned int destsize;
  unsigned int srcsize;
  unsigned int skipsize;
  unsigned int copysize;
  unsigned int i;

  /* Are we sub-sampling? */

  if (priv->subsample == AUDIO_SUBSAMPLE_NONE)
    {
      /* No.. do nothing to the buffer */

      return;
    }

  /* Yes.. we will need to subsample the newly received buffer in-place by
   * copying from the upper end of the buffer to the lower end.
   */

  src  = &apb->samp[apb->curbyte];
  dest = apb->samp;

  srcsize  = apb->nbytes - apb->curbyte;
  destsize = apb->nmaxbytes;

  /* This is the number of bytes that we need to skip between samples */

  skipsize = priv->align * (priv->subsample - 1);

  /* Let's deal with any partial samples from the last buffer */

  if (priv->npartial > 0)
    {
      /* Let's get an impossible corner case out of the way.  What if we
       * received a tiny audio buffer.  So small, that it (plus any previous
       * sample) is smaller than one sample.
       */

      if (priv->npartial + srcsize < priv->align)
        {
          /* Update the partial sample size and return the unmodified
           * buffer.
           */

          priv->npartial += srcsize;
          return;
        }

      /* We do at least have enough to complete the sample.  If this data
       * does not resides at the correct position at the from of the audio
       * buffer, then we will need to copy it.
       */

      copysize = priv->align - priv->npartial;
      if (apb->curbyte > 0)
        {
          /* We have to copy down */

          for (i = 0; i < copysize; i++)
            {
              *dest++ = *src++;
            }
        }
      else
        {
          /* If the data is already position at the beginning of the audio
           * buffer, then just increment the buffer pointers around the
           * data.
           */

          src  += copysize;
          dest += copysize;
        }

      /* Update the number of bytes in the working buffer and reset the
       * skip value
       */

      priv->npartial = 0;
      srcsize       -= copysize;
      destsize      -= copysize;
      priv->skip     = skipsize;
    }

  /* Now loop until either the entire audio buffer has been sub-sampling.
   * This copy in place works because we know that the sub-sampled data
   * will always be smaller than the original data.
   */

  while (srcsize > 0)
    {
      /* Do we need to skip ahead in the buffer? */

      if (priv->skip > 0)
        {
          /* How much can we skip in this buffer?  Depends on the smaller
           * of (1) the number of bytes that we need to skip and (2) the
           * number of bytes available in the newly received audio buffer.
           */

          copysize    = MIN(priv->skip, srcsize);

          priv->skip -= copysize;
          src        += copysize;
          srcsize    -= copysize;
        }

      /* Copy the sample from the audio buffer into the working buffer. */

      else
        {
          /* Do we have space for the whole sample? */

          if (srcsize < priv->align)
            {
              /* No.. this is a partial copy */

              copysize       = srcsize;
              priv->npartial = srcsize;
            }
          else
            {
              /* Copy the whole sample and re-arm the skip size */

              copysize       = priv->align;
              priv->skip     = skipsize;
            }

          /* Now copy the sample from the end of audio buffer
           * to the beginning.
           */

          for (i = 0; i < copysize; i++)
            {
              *dest++ = *src++;
            }

          /* Updates bytes available in the source buffer and bytes
           * remaining in the destination buffer.
           */

          srcsize  -= copysize;
          destsize -= copysize;
        }
    }

  /* Update the size and offset data in the audio buffer */

  apb->curbyte = 0;
  apb->nbytes  = apb->nmaxbytes - destsize;
}
#endif

/****************************************************************************
 * Name: pcm_getcaps
 *
 * Description:
 *   This method is called to retrieve the lower-half device capabilities.
 *   It will be called with device type AUDIO_TYPE_QUERY to request the
 *   overall capabilities, such as to determine the types of devices
 *   supported audio formats supported, etc.
 *   Then it may be called once or more with reported supported device types
 *   to determine the specific capabilities of that device type
 *   (such as MP3 encoder, WMA encoder, PCM output, etc.).
 *
 ****************************************************************************/

static int pcm_getcaps(FAR struct audio_lowerhalf_s *dev, int type,
                       FAR struct audio_caps_s *caps)
{
  FAR struct pcm_decode_s *priv = (FAR struct pcm_decode_s *)dev;
  FAR struct audio_lowerhalf_s *lower;
  int ret;

  DEBUGASSERT(priv);

  /* Defer the operation to the lower device driver */

  lower = priv->lower;
  DEBUGASSERT(lower && lower->ops->getcaps);

  /* Get the capabilities of the lower-level driver */

  ret = lower->ops->getcaps(lower, type, caps);
  if (ret < 0)
    {
      auderr("ERROR: Lower getcaps() failed: %d\n", ret);
      return ret;
    }

  /* Modify the capabilities reported by the lower driver:
   * PCM is the only supported format that we will report,
   * regardless of what the lower driver  reported.
   */

  if (caps->ac_subtype == AUDIO_TYPE_QUERY)
    {
      caps->ac_format.hw = (1 << (AUDIO_FMT_PCM - 1));
    }

  return caps->ac_len;
}

/****************************************************************************
 * Name: pcm_configure
 *
 * Description:
 *   This method is called to bind the lower-level driver to the upper-level
 *   driver and to configure the driver for a specific mode of
 *   operation defined by the parameters selected in supplied device caps
 *   structure.  The lower-level device should perform any initialization
 *   needed to prepare for operations in the specified mode.  It should not,
 *   however, process any audio data until the start method is called.
 *
 ****************************************************************************/

#ifdef CONFIG_AUDIO_MULTI_SESSION
static int pcm_configure(FAR struct audio_lowerhalf_s *dev,
                         FAR void *session,
                         FAR const struct audio_caps_s *caps)
#else
static int pcm_configure(FAR struct audio_lowerhalf_s *dev,
                         FAR const struct audio_caps_s *caps)
#endif
{
  FAR struct pcm_decode_s *priv = (FAR struct pcm_decode_s *)dev;
  FAR struct audio_lowerhalf_s *lower;

  DEBUGASSERT(priv);

#ifndef CONFIG_AUDIO_EXCLUDE_FFORWARD
  /* Pick off commands to perform sub-sampling.  Those are done by this
   * decoder.  All of configuration settings are handled by the lower level
   * audio driver.
   */

  if (caps->ac_type == AUDIO_TYPE_PROCESSING &&
      caps->ac_format.hw == AUDIO_PU_SUBSAMPLE_FORWARD)
    {
      /* Configure sub-sampling and return to avoid forwarding the
       * configuration to the lower level
       * driver.
       */

      pcm_subsample_configure(priv, caps->ac_controls.b[0]);
      return OK;
    }
#endif

  /* Defer all other operations to the lower device driver */

  lower = priv->lower;
  DEBUGASSERT(lower && lower->ops->configure);

  audinfo("Defer to lower configure\n");
#ifdef CONFIG_AUDIO_MULTI_SESSION
  return lower->ops->configure(lower, session, caps);
#else
  return lower->ops->configure(lower, caps);
#endif
}

/****************************************************************************
 * Name: pcm_shutdown
 *
 * Description:
 *   This method is called when the driver is closed.  The lower half driver
 *   should stop processing audio data, including terminating any active
 *   output generation.  It should also disable the audio hardware and put
 *   it into the lowest possible power usage state.
 *
 *   Any enqueued Audio Pipeline Buffers that have not been
 *   processed / dequeued should be dequeued by this function.
 *
 ****************************************************************************/

static int pcm_shutdown(FAR struct audio_lowerhalf_s *dev)
{
  FAR struct pcm_decode_s *priv = (FAR struct pcm_decode_s *)dev;
  FAR struct audio_lowerhalf_s *lower;

  DEBUGASSERT(priv);

  /* We are no longer streaming audio */

  priv->streaming = false;

  /* Defer the operation to the lower device driver */

  lower = priv->lower;
  DEBUGASSERT(lower && lower->ops->start);

  audinfo("Defer to lower shutdown\n");
  return lower->ops->shutdown(lower);
}

/****************************************************************************
 * Name: pcm_start
 *
 * Description:
 *   Start audio streaming in the configured mode.
 *   For input and synthesis devices, this means it should begin sending
 *   streaming audio data.  For output or processing type device, it means
 *   it should begin processing of any enqueued Audio Pipeline Buffers.
 *
 ****************************************************************************/

#ifdef CONFIG_AUDIO_MULTI_SESSION
static int pcm_start(FAR struct audio_lowerhalf_s *dev, FAR void *session)
#else
static int pcm_start(FAR struct audio_lowerhalf_s *dev)
#endif
{
  FAR struct pcm_decode_s *priv = (FAR struct pcm_decode_s *)dev;
  FAR struct audio_lowerhalf_s *lower;

  DEBUGASSERT(priv);

  /* Defer the operation to the lower device driver */

  lower = priv->lower;
  DEBUGASSERT(lower && lower->ops->start);

  audinfo("Defer to lower start\n");
#ifdef CONFIG_AUDIO_MULTI_SESSION
  return lower->ops->start(lower, session);
#else
  return lower->ops->start(lower);
#endif
}

/****************************************************************************
 * Name: pcm_stop
 *
 * Description:
 *   Stop audio streaming and/or processing of enqueued Audio Pipeline
 *   Buffers
 *
 ****************************************************************************/

#ifndef CONFIG_AUDIO_EXCLUDE_STOP
#ifdef CONFIG_AUDIO_MULTI_SESSION
static int pcm_stop(FAR struct audio_lowerhalf_s *dev, FAR void *session)
#else
static int pcm_stop(FAR struct audio_lowerhalf_s *dev)
#endif
{
  FAR struct pcm_decode_s *priv = (FAR struct pcm_decode_s *)dev;
  FAR struct audio_lowerhalf_s *lower;

  DEBUGASSERT(priv);

  /* We are no longer streaming */

  priv->streaming = false;

  /* Defer the operation to the lower device driver */

  lower = priv->lower;
  DEBUGASSERT(lower && lower->ops->stop);

  audinfo("Defer to lower stop\n");
#ifdef CONFIG_AUDIO_MULTI_SESSION
  return lower->ops->stop(lower, session);
#else
  return lower->ops->stop(lower);
#endif
}
#endif /* CONFIG_AUDIO_EXCLUDE_STOP */

/****************************************************************************
 * Name: pcm_pause
 *
 * Description:
 *   Pause the audio stream.
 *   Should keep current playback context active in case a resume  is issued.
 *   Could be called and then followed by a stop.
 *
 ****************************************************************************/

#ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME
#ifdef CONFIG_AUDIO_MULTI_SESSION
static int pcm_pause(FAR struct audio_lowerhalf_s *dev, FAR void *session)
#else
static int pcm_pause(FAR struct audio_lowerhalf_s *dev)
#endif
{
  FAR struct pcm_decode_s *priv = (FAR struct pcm_decode_s *)dev;
  FAR struct audio_lowerhalf_s *lower;

  DEBUGASSERT(priv);

  /* Defer the operation to the lower device driver */

  lower = priv->lower;
  DEBUGASSERT(lower && lower->ops->pause);

  audinfo("Defer to lower pause\n");
#ifdef CONFIG_AUDIO_MULTI_SESSION
  return lower->ops->pause(lower, session);
#else
  return lower->ops->pause(lower);
#endif
}

/****************************************************************************
 * Name: pcm_resume
 *
 * Description:
 *   Resumes audio streaming after a pause.
 *
 ****************************************************************************/

#ifdef CONFIG_AUDIO_MULTI_SESSION
static int pcm_resume(FAR struct audio_lowerhalf_s *dev, FAR void *session)
#else
static int pcm_resume(FAR struct audio_lowerhalf_s *dev)
#endif
{
  FAR struct pcm_decode_s *priv = (FAR struct pcm_decode_s *)dev;
  FAR struct audio_lowerhalf_s *lower;

  DEBUGASSERT(priv);

  /* Defer the operation to the lower device driver */

  lower = priv->lower;
  DEBUGASSERT(lower && lower->ops->resume);

  audinfo("Defer to lower resume\n");
#ifdef CONFIG_AUDIO_MULTI_SESSION
  return lower->ops->resume(lower, session);
#else
  return lower->ops->resume(lower);
#endif
}
#endif /* CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME */

/****************************************************************************
 * Name: pcm_allocbuffer
 *
 * Description:
 *   Allocate an audio pipeline buffer.  This routine provides the
 *   lower-half driver with the opportunity to perform special buffer
 *   allocation if needed, such as allocating from a specific memory
 *   region (DMA-able, etc.).  If not supplied, then the top-half
 *   driver will perform a standard kumm_malloc using normal user-space
 *   memory region.
 *
 ****************************************************************************/

static int pcm_allocbuffer(FAR struct audio_lowerhalf_s *dev,
                           FAR struct audio_buf_desc_s *apb)
{
  FAR struct pcm_decode_s *priv = (FAR struct pcm_decode_s *)dev;
  FAR struct audio_lowerhalf_s *lower;

  DEBUGASSERT(priv);

  /* Defer the operation to the lower device driver */

  lower = priv->lower;
  DEBUGASSERT(lower && lower->ops->allocbuffer);

  audinfo("Defer to lower allocbuffer\n");
  return lower->ops->allocbuffer(lower, apb);
}

/****************************************************************************
 * Name: pcm_freebuffer
 *
 * Description:
 *   Free an audio pipeline buffer.  If the lower-level driver provides an
 *   allocbuffer routine, it should also provide the freebuffer routine to
 *   perform the free operation.
 *
 ****************************************************************************/

static int pcm_freebuffer(FAR struct audio_lowerhalf_s *dev,
                          FAR struct audio_buf_desc_s *apb)
{
  FAR struct pcm_decode_s *priv = (FAR struct pcm_decode_s *)dev;
  FAR struct audio_lowerhalf_s *lower;

  DEBUGASSERT(priv);

  /* Defer the operation to the lower device driver */

  lower = priv->lower;
  DEBUGASSERT(lower && lower->ops->freebuffer);

  audinfo("Defer to lower freebuffer, apb=%p\n", apb);
  return lower->ops->freebuffer(lower, apb);
}

/****************************************************************************
 * Name: pcm_enqueuebuffer
 *
 * Description:
 *   Enqueue a buffer for processing.  This is a non-blocking enqueue
 *   operation.  If the lower-half driver's buffer queue is full, then it
 *   should return an error code of -ENOMEM, and the upper-half driver can
 *   decide to either block the calling thread or deal with it in a non-
 *   blocking manner.
 *
 *   For each call to enqueuebuffer, the lower-half driver must call
 *   audio_dequeuebuffer when it is finished processing the bufferr, passing
 *   the previously enqueued apb and a dequeue status so that the upper-half
 *   driver can decide if a waiting thread needs to be release, if the
 *   dequeued buffer should be passed to the next block in the Audio
 *   Pipeline, etc.
 *
 ****************************************************************************/

static int pcm_enqueuebuffer(FAR struct audio_lowerhalf_s *dev,
                             FAR struct ap_buffer_s *apb)
{
  FAR struct pcm_decode_s *priv = (FAR struct pcm_decode_s *)dev;
  FAR struct audio_lowerhalf_s *lower;
  apb_samp_t bytesleft;
  int ret;

  DEBUGASSERT(priv);
  audinfo("Received buffer %p, streaming=%d\n", apb, priv->streaming);

  lower = priv->lower;
  DEBUGASSERT(lower && lower->ops->enqueuebuffer && lower->ops->configure);

  /* Are we streaming yet? */

  if (priv->streaming)
    {
      /* Yes, we are streaming */

      /* Check for the last audio buffer in the stream */

      if ((apb->flags & AUDIO_APB_FINAL) != 0)
        {
          /* Yes.. then we are no longer streaming */

          priv->streaming = false;
        }

#ifndef CONFIG_AUDIO_EXCLUDE_FFORWARD
      audinfo("Received: apb=%p curbyte=%d nbytes=%d flags=%04x\n",
              apb, apb->curbyte, apb->nbytes, apb->flags);

      /* Perform any necessary sub-sampling operations */

      pcm_subsample(priv, apb);
#endif

      /* Then give the audio buffer to the lower driver */

      audinfo("Pass to lower enqueuebuffer: apb=%p curbyte=%d nbytes=%d\n",
              apb, apb->curbyte, apb->nbytes);

      return lower->ops->enqueuebuffer(lower, apb);
    }

  /* No.. then this must be the first buffer that we have seen (since we
   * will error out out if the first buffer is smaller than the WAV file
   * header.  There is no attempt to reconstruct the full header from
   * fragments in multiple, tiny audio buffers).
   */

  bytesleft = apb->nbytes - apb->curbyte;
  audinfo("curbyte=%d nbytes=%d nmaxbytes=%d bytesleft=%d\n",
          apb->curbyte, apb->nbytes, apb->nmaxbytes, bytesleft);

  /* Parse and verify the candidate PCM WAV file header */

#ifndef CONFIG_AUDIO_FORMAT_RAW
  ssize_t headersize = pcm_parsewav(priv, &apb->samp[apb->curbyte],
                                    bytesleft);
  if (headersize > 0)
    {
      struct audio_caps_s caps;

      /* Configure the lower level for the number of channels, bitrate,
       * and sample bitwidth.
       */

      DEBUGASSERT(priv->samprate < 65535);

      caps.ac_len            = sizeof(struct audio_caps_s);
      caps.ac_type           = AUDIO_TYPE_OUTPUT;
      caps.ac_channels       = priv->nchannels;

      caps.ac_controls.hw[0] = (uint16_t)priv->samprate;
      caps.ac_controls.b[2]  = priv->bpsamp;

#ifdef CONFIG_AUDIO_MULTI_SESSION
      ret = lower->ops->configure(lower, priv->session, &caps);
#else
      ret = lower->ops->configure(lower, &caps);
#endif
      if (ret < 0)
        {
          auderr("ERROR: Failed to set PCM configuration: %d\n", ret);
          return ret;
        }

      /* Bump up the data offset */

      apb->curbyte += headersize;
#endif
#ifndef CONFIG_AUDIO_EXCLUDE_FFORWARD
      audinfo("Begin streaming: apb=%p curbyte=%d nbytes=%d\n",
              apb, apb->curbyte, apb->nbytes);

      /* Perform any necessary sub-sampling operations */

      pcm_subsample(priv, apb);
#endif

      /* Then give the audio buffer to the lower driver */

      audinfo(
           "Pass to lower enqueuebuffer: apb=%p curbyte=%d nbytes=%d\n",
            apb, apb->curbyte, apb->nbytes);

      ret = lower->ops->enqueuebuffer(lower, apb);
      if (ret == OK)
        {
          /* Now we are streaming.  Unless for some reason there is only
           * one audio buffer in the audio stream.  In that case, this
           * will be marked as the final buffer
           */

          priv->streaming = ((apb->flags & AUDIO_APB_FINAL) == 0);
          return OK;
        }

      /* The normal protocol for streaming errors is as follows:
       *
       * (1) Fail the enqueueing by returned a negated error value.  The
       *     upper level then knows that this buffer was not queue.
       * (2) Return all queued buffers to the caller using the
       *     AUDIO_CALLBACK_DEQUEUE callback
       * (3) Terminate playing using the AUDIO_CALLBACK_COMPLETE
       *     callback.
       *
       * In this case we fail on the very first buffer and we need only
       * do (1) and (3).
       */

#ifdef CONFIG_AUDIO_MULTI_SESSION
      priv->export.upper(priv->export.priv, AUDIO_CALLBACK_COMPLETE,
                         NULL, OK, NULL);
#else
      priv->export.upper(priv->export.priv, AUDIO_CALLBACK_COMPLETE,
                         NULL, OK);
#endif

#ifndef CONFIG_AUDIO_FORMAT_RAW
    }

  /* This is not a WAV file! */

  auderr("ERROR: Invalid PCM WAV file\n");
#endif

  return -EINVAL;
}

/****************************************************************************
 * Name: pcm_cancelbuffer
 *
 * Description:
 *   Cancel a previously enqueued buffer.
 *
 ****************************************************************************/

static int pcm_cancelbuffer(FAR struct audio_lowerhalf_s *dev,
                            FAR struct ap_buffer_s *apb)
{
  FAR struct pcm_decode_s *priv = (FAR struct pcm_decode_s *)dev;
  FAR struct audio_lowerhalf_s *lower;

  DEBUGASSERT(priv);

  /* Defer the operation to the lower device driver */

  lower = priv->lower;
  DEBUGASSERT(lower && lower->ops->cancelbuffer);

  audinfo("Defer to lower cancelbuffer, apb=%p\n", apb);
  return lower->ops->cancelbuffer(lower, apb);
}

/****************************************************************************
 * Name: pcm_ioctl
 *
 * Description:
 *   Lower-half logic may support platform-specific ioctl commands.
 *
 ****************************************************************************/

static int pcm_ioctl(FAR struct audio_lowerhalf_s *dev, int cmd,
                     unsigned long arg)
{
  FAR struct pcm_decode_s *priv = (FAR struct pcm_decode_s *)dev;
  FAR struct audio_lowerhalf_s *lower;

  DEBUGASSERT(priv);

  /* Defer the operation to the lower device driver */

  lower = priv->lower;
  DEBUGASSERT(lower && lower->ops->ioctl);

  audinfo("Defer to lower ioctl, cmd=%d arg=%ld\n", cmd, arg);
  return lower->ops->ioctl(lower, cmd, arg);
}

/****************************************************************************
 * Name: pcm_reserve
 *
 * Description:
 *   Reserve a session (may only be one per device or may be multiple) for
 *   use by a client.  Client software can open audio devices and issue
 *   AUDIOIOC_GETCAPS calls freely, but other operations require a
 *   reservation.  A session reservation will assign a context that must
 *   be passed with
 *
 ****************************************************************************/

#ifdef CONFIG_AUDIO_MULTI_SESSION
static int pcm_reserve(FAR struct audio_lowerhalf_s *dev, FAR void **session)
#else
static int pcm_reserve(FAR struct audio_lowerhalf_s *dev)
#endif
{
  FAR struct pcm_decode_s *priv = (FAR struct pcm_decode_s *)dev;
  FAR struct audio_lowerhalf_s *lower;
  int ret;

#ifdef CONFIG_AUDIO_MULTI_SESSION
  DEBUGASSERT(priv && session);
#else
  DEBUGASSERT(priv);
#endif

  /* It is not necessary to reserve the upper half.  What we really need to
   * do is to reserved the lower device driver for exclusive use by the PCM
   * decoder.  That effectively reserves the upper PCM decoder along with
   * the lower driver (which is then not available for use by other
   * decoders).
   *
   * We do, however, need to remember the session returned by the lower
   * level.
   */

  lower = priv->lower;
  DEBUGASSERT(lower && lower->ops->reserve);

  audinfo("Defer to lower reserve\n");
#ifdef CONFIG_AUDIO_MULTI_SESSION
  ret = lower->ops->reserve(lower, &priv->session);

  /* Return a copy of the session to the caller */

  *session = priv->session;

#else
  ret = lower->ops->reserve(lower);
#endif

  return ret;
}

/****************************************************************************
 * Name: pcm_release
 *
 * Description:
 *   Release a session.
 *
 ****************************************************************************/

#ifdef CONFIG_AUDIO_MULTI_SESSION
static int pcm_release(FAR struct audio_lowerhalf_s *dev, FAR void *session)
#else
static int pcm_release(FAR struct audio_lowerhalf_s *dev)
#endif
{
  FAR struct pcm_decode_s *priv = (FAR struct pcm_decode_s *)dev;
  FAR struct audio_lowerhalf_s *lower;

  DEBUGASSERT(priv);

  /* Release the lower driver. It is then available for use by other
   * decoders (and we cannot use the lower driver either unless we re-
   * reserve it).
   */

  lower = priv->lower;
  DEBUGASSERT(lower && lower->ops->release);

  audinfo("Defer to lower release\n");
#ifdef CONFIG_AUDIO_MULTI_SESSION
  return lower->ops->release(lower, session);
#else
  return lower->ops->release(lower);
#endif
}

/****************************************************************************
 * Name: pcm_callback
 *
 * Description:
 *   Lower-to-upper level callback for buffer dequeueing.
 *
 * Input Parameters:
 *   priv - The value of the 'priv' field from out audio_lowerhalf_s.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#ifdef CONFIG_AUDIO_MULTI_SESSION
static void pcm_callback(FAR void *arg, uint16_t reason,
                         FAR struct ap_buffer_s *apb, uint16_t status,
                         FAR void *session)
#else
static void pcm_callback(FAR void *arg, uint16_t reason,
                         FAR struct ap_buffer_s *apb, uint16_t status)
#endif
{
  FAR struct pcm_decode_s *priv = (FAR struct pcm_decode_s *)arg;

  DEBUGASSERT(priv && priv->export.upper);

  /* The buffer belongs to an upper level.  Just forward the event to
   * the next level up.
   */

#ifdef CONFIG_AUDIO_MULTI_SESSION
  priv->export.upper(priv->export.priv, reason, apb, status, session);
#else
  priv->export.upper(priv->export.priv, reason, apb, status);
#endif
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: pcm_decode_initialize
 *
 * Description:
 *   Initialize the PCM device.  The PCM device accepts and contains a
 *   low-level audio DAC-type device.  It then returns a new audio lower
 *   half interface at adds a PCM decoding from end to the low-level
 *   audio device
 *
 * Input Parameters:
 *   dev - A reference to the low-level audio DAC-type device to contain.
 *
 * Returned Value:
 *   On success, a new audio device instance is returned that wraps the
 *   low-level device and provides a PCM decoding front end.  NULL is
 *   returned on failure.
 *
 ****************************************************************************/

FAR struct audio_lowerhalf_s *
  pcm_decode_initialize(FAR struct audio_lowerhalf_s *dev)
{
  FAR struct pcm_decode_s *priv;
  FAR struct audio_ops_s *ops;

  /* Allocate an instance of our private data structure */

  priv = (FAR struct pcm_decode_s *)kmm_zalloc(sizeof(struct pcm_decode_s));
  if (!priv)
    {
      auderr("ERROR: Failed to allocate driver structure\n");
      return NULL;
    }

  /* Initialize our private data structure.  Since kmm_zalloc() was used for
   * the allocation, we need to initialize only non-zero, non-NULL, non-
   * false fields.
   */

  /* Setup our operations */

  ops                  = &priv->ops;
  ops->getcaps         = pcm_getcaps;
  ops->configure       = pcm_configure;
  ops->shutdown        = pcm_shutdown;
  ops->start           = pcm_start;

#ifndef CONFIG_AUDIO_EXCLUDE_STOP
  ops->stop            = pcm_stop;
#endif

#ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME
  ops->pause           = pcm_pause;
  ops->resume          = pcm_resume;
#endif

  if (dev->ops->allocbuffer)
    {
      DEBUGASSERT(dev->ops->freebuffer);
      ops->allocbuffer = pcm_allocbuffer;
      ops->freebuffer  = pcm_freebuffer;
    }

  ops->enqueuebuffer   = pcm_enqueuebuffer;
  ops->cancelbuffer    = pcm_cancelbuffer;
  ops->ioctl           = pcm_ioctl;
  ops->reserve         = pcm_reserve;
  ops->release         = pcm_release;

  /* Set up our struct audio_lower_half that we will register with the
   * system.  The registration process will fill in the priv->export.upper
   * and priv fields with the correct callback information.
   */

  priv->export.ops     = &priv->ops;

  /* Save the struct audio_lower_half of the low-level audio device.  Set
   * out callback information for the lower-level audio device.  Our
   * callback will simply forward to the upper callback.
   */

  priv->lower          = dev;
  dev->upper           = pcm_callback;
  dev->priv            = priv;

  return &priv->export;
}

#endif /* CONFIG_AUDIO && CONFIG_AUDIO_FORMAT_PCM */