This is an automated email from the ASF dual-hosted git repository.

acassis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx-apps.git

commit 19a30364583b93e4aa52820d659f74562709f0ac
Author: Megha Rajput <[email protected]>
AuthorDate: Wed Nov 4 18:01:48 2026 +0000

    system/nxplayer: Parse WAV headers and retain PCM fallback.
    
    Parse RIFF/WAVE headers to obtain sample rate, channel count, and sample 
format for PCM S16_LE playback. Preserve whole-file PCM fallback for devices 
that parse WAV headers themselves, and rewind seekable input when parsing fails.
    
    Assisted-by: ChatGPT:GPT-6
    
    Signed-off-by: Megha Rajput <[email protected]>
---
 include/system/nxplayer.h      |  16 ++
 system/nxplayer/CMakeLists.txt |   3 +
 system/nxplayer/Makefile       |   6 +
 system/nxplayer/nxplayer.c     | 270 ++++++++++++++++++---
 system/nxplayer/nxplayer_wav.c | 522 +++++++++++++++++++++++++++++++++++++++++
 5 files changed, 790 insertions(+), 27 deletions(-)

diff --git a/include/system/nxplayer.h b/include/system/nxplayer.h
index 4508ae86e..d2c7f7761 100644
--- a/include/system/nxplayer.h
+++ b/include/system/nxplayer.h
@@ -31,6 +31,7 @@
 
 #include <mqueue.h>
 #include <pthread.h>
+#include <stdbool.h>
 
 /****************************************************************************
  * Pre-processor Definitions
@@ -97,6 +98,14 @@ struct nxplayer_s
 
   FAR const struct nxplayer_dec_ops_s *ops;
   struct nxplayer_tone_s tone;
+  uint32_t        wav_data_remaining;
+  uint64_t        wav_file_remaining;
+  uint64_t        wav_header_size;
+  uint8_t         wav_block_align;
+  int             wav_error;
+  bool            wav_playback;
+  bool            wav_passthrough;
+  bool            wav_fallback_ready;
 };
 
 typedef int (*nxplayer_func)(FAR struct nxplayer_s *pplayer, char *pargs);
@@ -599,6 +608,13 @@ int nxplayer_fill_mp3(int fd, FAR struct ap_buffer_s *apb);
 int nxplayer_fill_common(FAR struct nxplayer_s *pplayer, int fd,
                          FAR struct ap_buffer_s *apb);
 
+#if defined(CONFIG_AUDIO_FORMAT_PCM) && \
+    defined(CONFIG_NXPLAYER_FMT_FROM_EXT)
+extern const struct nxplayer_dec_ops_s g_wav_dec_ops;
+
+bool nxplayer_is_wav_file(FAR const char *pfilename);
+#endif
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/system/nxplayer/CMakeLists.txt b/system/nxplayer/CMakeLists.txt
index f167991d6..a89b48ec8 100644
--- a/system/nxplayer/CMakeLists.txt
+++ b/system/nxplayer/CMakeLists.txt
@@ -30,6 +30,9 @@ if(CONFIG_SYSTEM_NXPLAYER)
   target_sources(apps PRIVATE nxplayer.c)
 
   set(CSRCS nxplayer.c nxplayer_common.c nxplayer_mp3.c nxplayer_sbc.c)
+  if(CONFIG_AUDIO_FORMAT_PCM AND CONFIG_NXPLAYER_FMT_FROM_EXT)
+    list(APPEND CSRCS nxplayer_wav.c)
+  endif()
 
   target_sources(apps PRIVATE ${CSRCS})
 endif()
diff --git a/system/nxplayer/Makefile b/system/nxplayer/Makefile
index 777a77b7b..3c77dfc4d 100644
--- a/system/nxplayer/Makefile
+++ b/system/nxplayer/Makefile
@@ -29,6 +29,12 @@ CSRCS    += nxplayer_common.c
 CSRCS    += nxplayer_mp3.c
 CSRCS    += nxplayer_sbc.c
 
+ifneq ($(CONFIG_AUDIO_FORMAT_PCM),)
+ifneq ($(CONFIG_NXPLAYER_FMT_FROM_EXT),)
+CSRCS    += nxplayer_wav.c
+endif
+endif
+
 ifneq ($(CONFIG_NXPLAYER_COMMAND_LINE),)
 PROGNAME  = nxplayer
 PRIORITY  = SCHED_PRIORITY_DEFAULT
diff --git a/system/nxplayer/nxplayer.c b/system/nxplayer/nxplayer.c
index e20ddec64..dc4713c0d 100644
--- a/system/nxplayer/nxplayer.c
+++ b/system/nxplayer/nxplayer.c
@@ -100,9 +100,12 @@ int nxplayer_getmp3subformat(int fd);
 #ifdef CONFIG_AUDIO_FORMAT_SBC
 int nxplayer_getsbcsubformat(int fd);
 #endif
-/****************************************************************************
- * Private Data
- ****************************************************************************/
+
+static int nxplayer_playfile_impl(FAR struct nxplayer_s *pplayer,
+                                  FAR const char *pfilename, int filefmt,
+                                  int subfmt, uint8_t nchannels,
+                                  uint8_t bpsamp, uint32_t samprate,
+                                  uint8_t chmap, bool file_playback);
 
 #ifdef CONFIG_NXPLAYER_FMT_FROM_EXT
 static const struct nxplayer_ext_fmt_s g_known_ext[] =
@@ -136,6 +139,7 @@ static const struct nxplayer_ext_fmt_s g_known_ext[] =
 
 static const int g_known_ext_count = sizeof(g_known_ext) /
                     sizeof(struct nxplayer_ext_fmt_s);
+#endif /* CONFIG_NXPLAYER_FMT_FROM_EXT */
 
 static const struct nxplayer_dec_ops_s g_dec_ops[] =
 {
@@ -156,8 +160,6 @@ static const struct nxplayer_dec_ops_s g_dec_ops[] =
   }
 };
 
-#endif /* CONFIG_NXPLAYER_FMT_FROM_EXT */
-
 /****************************************************************************
  * Private Functions
  ****************************************************************************/
@@ -845,6 +847,7 @@ static FAR void *nxplayer_playthread(pthread_addr_t pvarg)
   bool                    running = true;
   bool                    streaming = true;
   bool                    failed = false;
+  int                     wav_error;
   struct ap_buffer_info_s buf_info;
   unsigned int            prio;
 #ifdef CONFIG_DEBUG_FEATURES
@@ -1236,6 +1239,8 @@ err_out:
 
   pthread_mutex_lock(&pplayer->mutex);
 
+  wav_error = pplayer->wav_error;
+
   /* Close the files */
 
   if (0 < pplayer->fd)
@@ -1249,14 +1254,28 @@ err_out:
   mq_close(pplayer->mq);                  /* Close the message queue */
   mq_unlink(pplayer->mqname);             /* Unlink the message queue */
   pplayer->ops   = NULL;                  /* Clear offload parser */
-  pplayer->state = NXPLAYER_STATE_IDLE;   /* Go to IDLE */
 
-  /* Clear the tone field */
+  /* Clear the tone and WAV fields */
 
   memset(&pplayer->tone, 0, sizeof(pplayer->tone));
+  pplayer->wav_playback = false;
+  pplayer->wav_data_remaining = 0;
+  pplayer->wav_file_remaining = 0;
+  pplayer->wav_header_size = 0;
+  pplayer->wav_block_align = 0;
+  pplayer->wav_error = 0;
+  pplayer->wav_passthrough = false;
+  pplayer->wav_fallback_ready = false;
+
+  pplayer->state = NXPLAYER_STATE_IDLE;   /* Go to IDLE */
 
   pthread_mutex_unlock(&pplayer->mutex);
 
+  if (wav_error < 0)
+    {
+      printf("ERROR: WAV playback read failed: %d\n", -wav_error);
+    }
+
   /* The playthread is done with the context.  Release it, which may
    * actually cause the context to be freed if the creator has already
    * abandoned (released) the context too.
@@ -1864,16 +1883,38 @@ static int nxplayer_playinternal(FAR struct nxplayer_s 
*pplayer,
   struct mq_attr      attr;
   struct sched_param  sparam;
   pthread_attr_t      tattr;
-  struct audio_caps_desc_s cap_desc;
+  struct audio_caps_desc_s cap_desc =
+    {
+      0
+    };
+
   struct ap_buffer_info_s  buf_info;
   struct audio_caps_s      caps;
   int                      min_channels;
+#if defined(CONFIG_AUDIO_FORMAT_PCM) && \
+    defined(CONFIG_NXPLAYER_FMT_FROM_EXT)
+  int                      index;
+#endif
   bool                     reserved = false;
+#if defined(CONFIG_AUDIO_FORMAT_PCM) && \
+    defined(CONFIG_NXPLAYER_FMT_FROM_EXT)
+  bool                     supported;
+#endif
   int                 ret;
 
   /* Try to open the device */
 
+#if defined(CONFIG_AUDIO_FORMAT_PCM) && \
+    defined(CONFIG_NXPLAYER_FMT_FROM_EXT)
+  pplayer->wav_passthrough = pplayer->wav_playback;
   ret = nxplayer_opendevice(pplayer, filefmt, subfmt);
+  if (ret < 0 && pplayer->wav_playback)
+    {
+      ret = nxplayer_opendevice(pplayer, filefmt, AUDIO_FMT_UNDEF);
+    }
+#else
+  ret = nxplayer_opendevice(pplayer, filefmt, subfmt);
+#endif
   if (ret < 0)
     {
       /* Error opening the device */
@@ -1915,6 +1956,55 @@ static int nxplayer_playinternal(FAR struct nxplayer_s 
*pplayer,
           ret = -EINVAL;
           goto err_out;
         }
+
+#if defined(CONFIG_AUDIO_FORMAT_PCM) && \
+    defined(CONFIG_NXPLAYER_FMT_FROM_EXT)
+      if (pplayer->wav_playback)
+        {
+          caps.ac_len = sizeof(caps);
+          caps.ac_type = AUDIO_TYPE_QUERY;
+          caps.ac_subtype = filefmt;
+          caps.ac_format.b[0] = 0;
+          supported = false;
+          if (ioctl(pplayer->dev_fd, AUDIOIOC_GETCAPS,
+                    (unsigned long)&caps) == caps.ac_len)
+            {
+              for (index = 0; index < sizeof(caps.ac_controls.b); index++)
+                {
+                  if (caps.ac_controls.b[index] == subfmt)
+                    {
+                      supported = true;
+                      break;
+                    }
+
+                  if (caps.ac_controls.b[index] == AUDIO_SUBFMT_END)
+                    {
+                      break;
+                    }
+                }
+            }
+
+          pplayer->wav_passthrough = !supported;
+        }
+#endif
+    }
+
+#if defined(CONFIG_AUDIO_FORMAT_PCM) && \
+    defined(CONFIG_NXPLAYER_FMT_FROM_EXT)
+  else if (pplayer->wav_playback)
+    {
+      pplayer->wav_passthrough = true;
+    }
+#endif
+
+  if (pplayer->wav_passthrough)
+    {
+      if (lseek(pplayer->fd, 0, SEEK_SET) != 0)
+        {
+          ret = errno > 0 ? -errno : -EIO;
+          auderr("ERROR: Failed to rewind WAV file: %d\n", ret);
+          goto err_out;
+        }
     }
 
   if (nchannels && samprate && bpsamp)
@@ -1929,9 +2019,27 @@ static int nxplayer_playinternal(FAR struct nxplayer_s 
*pplayer,
       cap_desc.caps.ac_controls.hw[0] = samprate;
       cap_desc.caps.ac_controls.b[3]  = samprate >> 16;
       cap_desc.caps.ac_controls.b[2]  = bpsamp;
+#if defined(CONFIG_AUDIO_FORMAT_PCM) && \
+    defined(CONFIG_NXPLAYER_FMT_FROM_EXT)
+      if (pplayer->wav_playback && !pplayer->wav_passthrough)
+        {
+          cap_desc.caps.ac_format.b[0] = subfmt;
+        }
+
+#endif
       cap_desc.caps.ac_subtype        = filefmt;
 
-      ioctl(pplayer->dev_fd, AUDIOIOC_CONFIGURE, (unsigned long)&cap_desc);
+      ret = ioctl(pplayer->dev_fd, AUDIOIOC_CONFIGURE,
+                  (unsigned long)&cap_desc);
+#if defined(CONFIG_AUDIO_FORMAT_PCM) && \
+    defined(CONFIG_NXPLAYER_FMT_FROM_EXT)
+      if (pplayer->wav_playback && ret < 0)
+        {
+          ret = -errno;
+          auderr("ERROR: Failed to configure WAV audio format: %d\n", ret);
+          goto err_out;
+        }
+#endif
     }
 
   /* Query the audio device for its preferred buffer count */
@@ -2048,25 +2156,22 @@ err_out:
 int nxplayer_playfile(FAR struct nxplayer_s *pplayer,
                       FAR const char *pfilename, int filefmt, int subfmt)
 {
-  return nxplayer_playraw(pplayer, pfilename, filefmt,
-                          subfmt, 0, 0, 0, 0);
+  return nxplayer_playfile_impl(pplayer, pfilename, filefmt, subfmt,
+                                0, 0, 0, 0, true);
 }
 
 /****************************************************************************
- * Name: nxplayer_playraw
+ * Name: nxplayer_playfile_impl
  *
- *   nxplayer_playraw() tries to play the audio file using the Audio
- *   system.  If a preferred device is specified, it will try to use that
- *   device otherwise it will perform a search of the Audio device files
- *   to find a suitable device.
+ *   Open and configure a file or raw PCM stream for playback.
  *
  * Input:
  *   pplayer    Pointer to the initialized MPlayer context
  *   pfilename  Pointer to the filename to play
  *   filefmt   - Format of audio in filename if known, AUDIO_FMT_UNDEF
- *               to let nxplayer_playraw() determine automatically.
+ *               to determine automatically.
  *   subfmt    - Sub-Format of audio in filename if known, AUDIO_FMT_UNDEF
- *               to let nxplayer_playraw() determine automatically.
+ *               to determine automatically.
  *   nchannels  channel num
  *   bpsampe    bit width
  *   samprate   sample rate
@@ -2081,10 +2186,12 @@ int nxplayer_playfile(FAR struct nxplayer_s *pplayer,
  *
  ****************************************************************************/
 
-int nxplayer_playraw(FAR struct nxplayer_s *pplayer,
-                     FAR const char *pfilename,
-                     int filefmt, int subfmt, uint8_t nchannels,
-                     uint8_t bpsamp, uint32_t samprate, uint8_t chmap)
+static int nxplayer_playfile_impl(FAR struct nxplayer_s *pplayer,
+                                  FAR const char *pfilename,
+                                  int filefmt, int subfmt,
+                                  uint8_t nchannels, uint8_t bpsamp,
+                                  uint32_t samprate, uint8_t chmap,
+                                  bool file_playback)
 {
 #ifdef CONFIG_NXPLAYER_INCLUDE_MEDIADIR
   char path[PATH_MAX];
@@ -2092,6 +2199,13 @@ int nxplayer_playraw(FAR struct nxplayer_s *pplayer,
   int  tmpsubfmt = AUDIO_FMT_UNDEF;
   int  ret;
   int  c;
+#if defined(CONFIG_AUDIO_FORMAT_PCM) && \
+    defined(CONFIG_NXPLAYER_FMT_FROM_EXT)
+  uint32_t original_samprate;
+  uint8_t  original_nchannels;
+  uint8_t  original_bpsamp;
+  int      original_subfmt;
+#endif
 
   DEBUGASSERT(pplayer != NULL);
   DEBUGASSERT(pfilename != NULL);
@@ -2101,6 +2215,15 @@ int nxplayer_playraw(FAR struct nxplayer_s *pplayer,
       return -EBUSY;
     }
 
+  pplayer->wav_playback = false;
+  pplayer->wav_data_remaining = 0;
+  pplayer->wav_file_remaining = 0;
+  pplayer->wav_header_size = 0;
+  pplayer->wav_block_align = 0;
+  pplayer->wav_error = 0;
+  pplayer->wav_passthrough = false;
+  pplayer->wav_fallback_ready = false;
+
   audinfo("==============================\n");
   audinfo("Playing file %s\n", pfilename);
   audinfo("==============================\n");
@@ -2178,12 +2301,25 @@ int nxplayer_playraw(FAR struct nxplayer_s *pplayer,
       subfmt = tmpsubfmt;
     }
 
-  for (c = 0; c < nitems(g_dec_ops); c++)
+  pplayer->ops = NULL;
+
+#if defined(CONFIG_NXPLAYER_FMT_FROM_EXT) && \
+    defined(CONFIG_AUDIO_FORMAT_PCM)
+  if (file_playback && filefmt == AUDIO_FMT_PCM &&
+      nxplayer_is_wav_file(pfilename))
     {
-      if (g_dec_ops[c].format == filefmt)
+      pplayer->ops = &g_wav_dec_ops;
+    }
+  else
+#endif
+    {
+      for (c = 0; c < nitems(g_dec_ops); c++)
         {
-          pplayer->ops = &g_dec_ops[c];
-          break;
+          if (g_dec_ops[c].format == filefmt)
+            {
+              pplayer->ops = &g_dec_ops[c];
+              break;
+            }
         }
     }
 
@@ -2195,11 +2331,59 @@ int nxplayer_playraw(FAR struct nxplayer_s *pplayer,
 
   if (pplayer->ops->pre_parse)
     {
+#if defined(CONFIG_AUDIO_FORMAT_PCM) && \
+    defined(CONFIG_NXPLAYER_FMT_FROM_EXT)
+      original_samprate = samprate;
+      original_nchannels = nchannels;
+      original_bpsamp = bpsamp;
+      original_subfmt = subfmt;
+#endif
+
       ret = pplayer->ops->pre_parse(pplayer, pplayer->fd, &samprate,
                                     &nchannels, &bpsamp, &subfmt);
       if (ret < 0)
         {
-          goto err_out;
+#if defined(CONFIG_AUDIO_FORMAT_PCM) && \
+    defined(CONFIG_NXPLAYER_FMT_FROM_EXT)
+          if (pplayer->ops == &g_wav_dec_ops &&
+              pplayer->wav_fallback_ready)
+            {
+              pplayer->ops = NULL;
+              for (c = 0; c < nitems(g_dec_ops); c++)
+                {
+                  if (g_dec_ops[c].format == AUDIO_FMT_PCM)
+                    {
+                      pplayer->ops = &g_dec_ops[c];
+                      break;
+                    }
+                }
+
+              if (!pplayer->ops)
+                {
+                  goto err_out;
+                }
+
+              samprate = original_samprate;
+              nchannels = original_nchannels;
+              bpsamp = original_bpsamp;
+              subfmt = original_subfmt;
+              pplayer->wav_playback = false;
+              pplayer->wav_data_remaining = 0;
+              pplayer->wav_file_remaining = 0;
+              pplayer->wav_header_size = 0;
+              pplayer->wav_block_align = 0;
+              pplayer->wav_error = 0;
+              pplayer->wav_passthrough = false;
+              pplayer->wav_fallback_ready = false;
+
+              audinfo("WAV parsing failed; using PCM decoder\n");
+              ret = OK;
+            }
+          else
+#endif
+            {
+              goto err_out;
+            }
         }
     }
 
@@ -2230,10 +2414,34 @@ int nxplayer_playraw(FAR struct nxplayer_s *pplayer,
 err_out:
   close(pplayer->fd);
   pplayer->fd = -1;
+  pplayer->wav_playback = false;
+  pplayer->wav_data_remaining = 0;
+  pplayer->wav_file_remaining = 0;
+  pplayer->wav_header_size = 0;
+  pplayer->wav_block_align = 0;
+  pplayer->wav_error = 0;
+  pplayer->wav_passthrough = false;
+  pplayer->wav_fallback_ready = false;
 
   return ret;
 }
 
+/****************************************************************************
+ * Name: nxplayer_playraw
+ *
+ *   Play a raw media stream using the supplied format parameters.
+ *
+ ****************************************************************************/
+
+int nxplayer_playraw(FAR struct nxplayer_s *pplayer,
+                     FAR const char *pfilename,
+                     int filefmt, int subfmt, uint8_t nchannels,
+                     uint8_t bpsamp, uint32_t samprate, uint8_t chmap)
+{
+  return nxplayer_playfile_impl(pplayer, pfilename, filefmt, subfmt,
+                                nchannels, bpsamp, samprate, chmap, false);
+}
+
 /****************************************************************************
  * Name: nxplayer_playtone
  *
@@ -2326,6 +2534,14 @@ FAR struct nxplayer_s *nxplayer_create(void)
   pplayer->state = NXPLAYER_STATE_IDLE;
   pplayer->dev_fd = -1;
   pplayer->fd = -1;
+  pplayer->wav_playback = false;
+  pplayer->wav_data_remaining = 0;
+  pplayer->wav_file_remaining = 0;
+  pplayer->wav_header_size = 0;
+  pplayer->wav_block_align = 0;
+  pplayer->wav_error = 0;
+  pplayer->wav_passthrough = false;
+  pplayer->wav_fallback_ready = false;
 #ifdef CONFIG_NXPLAYER_INCLUDE_PREFERRED_DEVICE
   pplayer->prefdevice[0] = '\0';
   pplayer->prefformat = 0;
diff --git a/system/nxplayer/nxplayer_wav.c b/system/nxplayer/nxplayer_wav.c
new file mode 100644
index 000000000..994988a17
--- /dev/null
+++ b/system/nxplayer/nxplayer_wav.c
@@ -0,0 +1,522 @@
+/****************************************************************************
+ * apps/system/nxplayer/nxplayer_wav.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * 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 <errno.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <nuttx/audio/audio.h>
+#include <nuttx/debug.h>
+
+#include "system/nxplayer.h"
+
+#if defined(CONFIG_AUDIO_FORMAT_PCM) && \
+    defined(CONFIG_NXPLAYER_FMT_FROM_EXT)
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+static uint32_t nxplayer_wav_rate_cap(uint32_t rate);
+
+static uint16_t nxplayer_wav_le16(FAR const uint8_t *data)
+{
+  return (uint16_t)data[0] | ((uint16_t)data[1] << 8);
+}
+
+static uint32_t nxplayer_wav_le32(FAR const uint8_t *data)
+{
+  return (uint32_t)data[0] | ((uint32_t)data[1] << 8) |
+         ((uint32_t)data[2] << 16) | ((uint32_t)data[3] << 24);
+}
+
+static int nxplayer_wav_read_exact(int fd, FAR uint8_t *buffer,
+                                   size_t length)
+{
+  size_t offset = 0;
+
+  while (offset < length)
+    {
+      ssize_t ret = read(fd, buffer + offset, length - offset);
+
+      if (ret < 0)
+        {
+          if (errno == EINTR)
+            {
+              continue;
+            }
+
+          return -errno;
+        }
+
+      if (ret == 0)
+        {
+          return -EINVAL;
+        }
+
+      offset += ret;
+    }
+
+  return OK;
+}
+
+static int nxplayer_parse_wav_data(int fd, FAR uint32_t *samprate,
+                                   FAR uint8_t *nchannels,
+                                   FAR uint8_t *bpsamp,
+                                   FAR uint32_t *data_length,
+                                   FAR uint64_t *stream_length,
+                                   FAR uint64_t *header_size,
+                                   FAR bool *fallback_ready)
+{
+  uint8_t header[12];
+  uint8_t chunk[8];
+  uint8_t format[16];
+  off_t file_end;
+  uint64_t riff_end;
+  uint64_t position = sizeof(header);
+  uint64_t data_offset = 0;
+  uint32_t data_size = 0;
+  uint32_t rate = 0;
+  uint32_t byte_rate = 0;
+  uint16_t channels = 0;
+  uint16_t block_align = 0;
+  uint16_t bits = 0;
+  bool have_format = false;
+  bool have_data = false;
+  int ret;
+
+  *fallback_ready = true;
+  file_end = lseek(fd, 0, SEEK_END);
+  if (file_end < 0)
+    {
+      *fallback_ready = errno == ESPIPE || errno == ENOSYS;
+      return -errno;
+    }
+
+  *fallback_ready = false;
+  if (file_end < (off_t)sizeof(header))
+    {
+      ret = -EINVAL;
+      goto err_out;
+    }
+
+  if (lseek(fd, 0, SEEK_SET) != 0)
+    {
+      ret = errno > 0 ? -errno : -EIO;
+      goto err_out;
+    }
+
+  ret = nxplayer_wav_read_exact(fd, header, sizeof(header));
+  if (ret < 0)
+    {
+      goto err_out;
+    }
+
+  if (memcmp(header, "RIFF", 4) != 0 ||
+      memcmp(header + 8, "WAVE", 4) != 0)
+    {
+      ret = -EINVAL;
+      goto err_out;
+    }
+
+  riff_end = (uint64_t)nxplayer_wav_le32(header + 4) + 8;
+  if (riff_end < sizeof(header) || riff_end > (uint64_t)file_end)
+    {
+      ret = -EINVAL;
+      goto err_out;
+    }
+
+  while (position < riff_end)
+    {
+      uint32_t chunk_size;
+      uint64_t next;
+
+      if (riff_end - position < sizeof(chunk) ||
+          lseek(fd, (off_t)position, SEEK_SET) != (off_t)position)
+        {
+          ret = -EINVAL;
+          goto err_out;
+        }
+
+      ret = nxplayer_wav_read_exact(fd, chunk, sizeof(chunk));
+      if (ret < 0)
+        {
+          goto err_out;
+        }
+
+      chunk_size = nxplayer_wav_le32(chunk + 4);
+      next = position + sizeof(chunk) + chunk_size + (chunk_size & 1);
+      if (next > riff_end || next > (uint64_t)file_end)
+        {
+          ret = -EINVAL;
+          goto err_out;
+        }
+
+      if (memcmp(chunk, "fmt ", 4) == 0)
+        {
+          uint64_t expected_rate;
+
+          if (have_format || chunk_size < sizeof(format))
+            {
+              ret = -EINVAL;
+              goto err_out;
+            }
+
+          ret = nxplayer_wav_read_exact(fd, format, sizeof(format));
+          if (ret < 0)
+            {
+              goto err_out;
+            }
+
+          if (nxplayer_wav_le16(format) != 1)
+            {
+              ret = -ENOTSUP;
+              goto err_out;
+            }
+
+          channels = nxplayer_wav_le16(format + 2);
+          rate = nxplayer_wav_le32(format + 4);
+          byte_rate = nxplayer_wav_le32(format + 8);
+          block_align = nxplayer_wav_le16(format + 12);
+          bits = nxplayer_wav_le16(format + 14);
+          expected_rate = (uint64_t)rate * channels * bits / 8;
+
+          if ((channels != 1 && channels != 2) || rate == 0 ||
+              bits != 16 || block_align != channels * 2 ||
+              expected_rate > UINT32_MAX || byte_rate != expected_rate)
+            {
+              ret = -EINVAL;
+              goto err_out;
+            }
+
+          have_format = true;
+        }
+      else if (memcmp(chunk, "data", 4) == 0)
+        {
+          if (have_data)
+            {
+              ret = -EINVAL;
+              goto err_out;
+            }
+
+          data_offset = position + sizeof(chunk);
+          data_size = chunk_size;
+          have_data = true;
+        }
+
+      position = next;
+    }
+
+  if (!have_format || !have_data || data_size == 0 ||
+      data_size % block_align != 0)
+    {
+      ret = -EINVAL;
+      goto err_out;
+    }
+
+  if (lseek(fd, (off_t)data_offset, SEEK_SET) != (off_t)data_offset)
+    {
+      ret = errno > 0 ? -errno : -EIO;
+      goto err_out;
+    }
+
+  *samprate = rate;
+  *nchannels = (uint8_t)channels;
+  *bpsamp = (uint8_t)bits;
+  *data_length = data_size;
+  *stream_length = data_offset + data_size;
+  *header_size = data_offset;
+  return OK;
+
+err_out:
+  if (lseek(fd, 0, SEEK_SET) == 0)
+    {
+      *fallback_ready = true;
+    }
+  else
+    {
+      auderr("ERROR: Failed to rewind WAV file after parsing: %d\n", errno);
+    }
+
+  return ret;
+}
+
+static int nxplayer_parse_wav(FAR struct nxplayer_s *pplayer, int fd,
+                              FAR uint32_t *samplerate,
+                              FAR uint8_t *nchannels, FAR uint8_t *bpsamp,
+                              FAR int *subfmt)
+{
+  int ret;
+
+  ret = nxplayer_parse_wav_data(fd, samplerate, nchannels, bpsamp,
+                                &pplayer->wav_data_remaining,
+                                &pplayer->wav_file_remaining,
+                                &pplayer->wav_header_size,
+                                &pplayer->wav_fallback_ready);
+  if (ret < 0)
+    {
+      auderr("ERROR: Invalid or unsupported WAV file: %d\n", ret);
+      return ret;
+    }
+
+  if (nxplayer_wav_rate_cap(*samplerate) == 0)
+    {
+      auderr("ERROR: Unsupported WAV sample rate: %" PRIu32 "\n",
+             *samplerate);
+      if (lseek(fd, 0, SEEK_SET) == 0)
+        {
+          pplayer->wav_fallback_ready = true;
+        }
+      else
+        {
+          ret = errno > 0 ? -errno : -EIO;
+          auderr("ERROR: Failed to rewind WAV file after parsing: %d\n",
+                 -ret);
+          return ret;
+        }
+
+      return -ENOTSUP;
+    }
+
+  pplayer->wav_block_align = *nchannels * 2;
+  pplayer->wav_playback = true;
+  pplayer->wav_passthrough = false;
+  *bpsamp = 16;
+  *subfmt = AUDIO_SUBFMT_PCM_S16_LE;
+  return OK;
+}
+
+static int nxplayer_fill_wav(FAR struct nxplayer_s *pplayer, int fd,
+                             FAR struct ap_buffer_s *apb)
+{
+  size_t wanted;
+  size_t offset = 0;
+  size_t payload_capacity;
+  bool first = pplayer->wav_passthrough &&
+               pplayer->wav_header_size != 0;
+  int error;
+
+  apb->nbytes = 0;
+  apb->curbyte = 0;
+  apb->flags = 0;
+
+  if (pplayer->wav_passthrough)
+    {
+      wanted = pplayer->wav_file_remaining < apb->nmaxbytes ?
+               (size_t)pplayer->wav_file_remaining : apb->nmaxbytes;
+      if (first)
+        {
+          if (pplayer->wav_header_size > wanted)
+            {
+              error = -EMSGSIZE;
+              goto err_out;
+            }
+
+          payload_capacity = wanted - (size_t)pplayer->wav_header_size;
+          if (payload_capacity < pplayer->wav_block_align)
+            {
+              error = -EMSGSIZE;
+              goto err_out;
+            }
+
+          wanted -= payload_capacity % pplayer->wav_block_align;
+        }
+
+      while (offset < wanted)
+        {
+          ssize_t ret = read(fd, apb->samp + offset, wanted - offset);
+
+          if (ret < 0)
+            {
+              if (errno == EINTR)
+                {
+                  continue;
+                }
+
+              error = -errno;
+              goto passthrough_err_out;
+            }
+
+          if (ret == 0)
+            {
+              error = -EIO;
+              goto passthrough_err_out;
+            }
+
+          offset += ret;
+        }
+
+      apb->nbytes = offset;
+      pplayer->wav_file_remaining -= offset;
+      pplayer->wav_header_size = 0;
+      if (pplayer->wav_file_remaining == 0)
+        {
+          apb->flags |= AUDIO_APB_FINAL;
+          close(fd);
+          pplayer->fd = -1;
+        }
+
+      return OK;
+    }
+
+  wanted = apb->nmaxbytes < pplayer->wav_data_remaining ?
+           apb->nmaxbytes : pplayer->wav_data_remaining;
+  wanted -= wanted % pplayer->wav_block_align;
+  if (wanted == 0)
+    {
+      error = -EINVAL;
+      goto err_out;
+    }
+
+  while (offset < wanted)
+    {
+      ssize_t ret = read(fd, apb->samp + offset, wanted - offset);
+
+      if (ret < 0)
+        {
+          if (errno == EINTR)
+            {
+              continue;
+            }
+
+          apb->nbytes = offset - offset % pplayer->wav_block_align;
+          error = -errno;
+          goto err_out;
+        }
+
+      if (ret == 0)
+        {
+          apb->nbytes = offset - offset % pplayer->wav_block_align;
+          error = -EIO;
+          goto err_out;
+        }
+
+      offset += ret;
+    }
+
+  apb->nbytes = offset;
+  pplayer->wav_data_remaining -= offset;
+  if (pplayer->wav_data_remaining == 0)
+    {
+      apb->flags |= AUDIO_APB_FINAL;
+      close(fd);
+      pplayer->fd = -1;
+    }
+
+  return OK;
+
+passthrough_err_out:
+  if (first && offset < pplayer->wav_header_size)
+    {
+      apb->nbytes = 0;
+    }
+  else if (first)
+    {
+      size_t payload = offset - pplayer->wav_header_size;
+
+      payload -= payload % pplayer->wav_block_align;
+      apb->nbytes = pplayer->wav_header_size + payload;
+    }
+  else
+    {
+      apb->nbytes = offset - offset % pplayer->wav_block_align;
+    }
+
+err_out:
+  pplayer->wav_error = error;
+  apb->flags |= AUDIO_APB_FINAL;
+  close(fd);
+  pplayer->fd = -1;
+  return OK;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+bool nxplayer_is_wav_file(FAR const char *pfilename)
+{
+  FAR const char *extension = strrchr(pfilename, '.');
+  FAR const char *slash = strrchr(pfilename, '/');
+
+  return extension != NULL && (slash == NULL || extension > slash) &&
+         strcasecmp(extension + 1, "wav") == 0;
+}
+
+static uint32_t nxplayer_wav_rate_cap(uint32_t rate)
+{
+  switch (rate)
+    {
+      case 8000:
+        return AUDIO_SAMP_RATE_8K;
+      case 11025:
+        return AUDIO_SAMP_RATE_11K;
+      case 12000:
+        return AUDIO_SAMP_RATE_12K;
+      case 16000:
+        return AUDIO_SAMP_RATE_16K;
+      case 22050:
+        return AUDIO_SAMP_RATE_22K;
+      case 24000:
+        return AUDIO_SAMP_RATE_24K;
+      case 32000:
+        return AUDIO_SAMP_RATE_32K;
+      case 44100:
+        return AUDIO_SAMP_RATE_44K;
+      case 48000:
+        return AUDIO_SAMP_RATE_48K;
+      case 88200:
+        return AUDIO_SAMP_RATE_88K;
+      case 96000:
+        return AUDIO_SAMP_RATE_96K;
+      case 128000:
+        return AUDIO_SAMP_RATE_128K;
+      case 160000:
+        return AUDIO_SAMP_RATE_160K;
+      case 172000:
+        return AUDIO_SAMP_RATE_172K;
+      case 192000:
+        return AUDIO_SAMP_RATE_192K;
+      default:
+        return 0;
+    }
+}
+
+const struct nxplayer_dec_ops_s g_wav_dec_ops =
+{
+  AUDIO_FMT_PCM,
+  nxplayer_parse_wav,
+  nxplayer_fill_wav
+};
+
+#endif /* CONFIG_AUDIO_FORMAT_PCM && CONFIG_NXPLAYER_FMT_FROM_EXT */

Reply via email to