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 */
