summaryrefslogtreecommitdiff
path: root/nuttx/audio
diff options
context:
space:
mode:
authorGregory Nutt <gnutt@nuttx.org>2014-07-22 19:23:05 -0600
committerGregory Nutt <gnutt@nuttx.org>2014-07-22 19:23:05 -0600
commit7fed0c8ab356408ac990c26d4470084e97f68431 (patch)
tree2b86d50da4ee6f987d503cbde6abae042f5d5d44 /nuttx/audio
parentf510fe6d8e15b4761eabcffe406ba237e0982380 (diff)
downloadnuttx-7fed0c8ab356408ac990c26d4470084e97f68431.tar.gz
nuttx-7fed0c8ab356408ac990c26d4470084e97f68431.tar.bz2
nuttx-7fed0c8ab356408ac990c26d4470084e97f68431.zip
Flesh out a few more PCM methods, still incomplete. Re-vision PCM structure definition
Diffstat (limited to 'nuttx/audio')
-rw-r--r--nuttx/audio/audio.c5
-rw-r--r--nuttx/audio/pcm_decode.c397
2 files changed, 314 insertions, 88 deletions
diff --git a/nuttx/audio/audio.c b/nuttx/audio/audio.c
index 32e067bb6..f5f815431 100644
--- a/nuttx/audio/audio.c
+++ b/nuttx/audio/audio.c
@@ -386,9 +386,10 @@ static int audio_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
FAR struct audio_caps_s *caps = (FAR struct audio_caps_s*)((uintptr_t)arg);
DEBUGASSERT(lower->ops->getcaps != NULL);
- audvdbg("AUDIOIOC_GETCAPS: Device=%d", caps->ac_type);
+ audvdbg("AUDIOIOC_GETCAPS: Device=%d\n", caps->ac_type);
/* Call the lower-half driver capabilities handler */
+
ret = lower->ops->getcaps(lower, caps->ac_type, caps);
}
break;
@@ -399,7 +400,7 @@ static int audio_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
(FAR const struct audio_caps_desc_s*)((uintptr_t)arg);
DEBUGASSERT(lower->ops->configure != NULL);
- audvdbg("AUDIOIOC_INITIALIZE: Device=%d", caps->caps.ac_type);
+ audvdbg("AUDIOIOC_INITIALIZE: Device=%d\n", caps->caps.ac_type);
/* Call the lower-half driver configure handler */
diff --git a/nuttx/audio/pcm_decode.c b/nuttx/audio/pcm_decode.c
index 6cf7327fc..56cf7079b 100644
--- a/nuttx/audio/pcm_decode.c
+++ b/nuttx/audio/pcm_decode.c
@@ -64,6 +64,7 @@
****************************************************************************/
/* Configuration ************************************************************/
+#define CONFIG_PCM_DEBUG 1 /* For now */
/****************************************************************************
* Private Types
@@ -93,16 +94,41 @@ struct pcm_decode_s
*/
FAR struct audio_lowerhalf_s *lower;
+
+ /* This is a copy of the WAV file header, in host endian order */
+
+ struct wav_header_s wav;
+
+ /* Set to true once we have parse a valid header and have begun stream
+ * audio.
+ */
+
+ bool streaming;
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
+/* Helper functions *********************************************************/
+
#ifdef CONFIG_PCM_DEBUG
-static void pcm_dump(FAR const struct wav_header_s *wav)
+static void pcm_dump(FAR const struct wav_header_s *wav);
+#else
+# define pcm_dump(w)
#endif
+#ifdef CONFIG_ENDIAN_BIG
+static uint16_t pcm_leuint16(uint16_t value);
+static uint16_t pcm_leuint32(uint32_t value);
+#else
+# define pcm_leuint16(v) (v)
+# define pcm_leuint32(v) (v)
+#endif
+
+static inline bool pcm_validwav(FAR const struct wav_header_s *wav);
+static bool pcm_parsewav(FAR struct pcm_decode_s *priv, uint8_t *data);
+
/* struct audio_lowerhalf_s methods *****************************************/
static int pcm_getcaps(FAR struct audio_lowerhalf_s *dev, int type,
@@ -185,27 +211,63 @@ static int pcm_release(FAR struct audio_lowerhalf_s *dev);
* Name: pcm_dump
*
* Description:
- * Dump a WAV file header.
+ * Dump a WAV file header.
*
****************************************************************************/
#ifdef CONFIG_PCM_DEBUG
static void pcm_dump(FAR const struct wav_header_s *wav)
{
- printf( "Wave file header\n");
- printf( " Chunk ID: 0x%08x\n", wav->chkid);
- printf( " Chunk Size: %u\n", wav->chklen);
- printf( " Format: 0x%08x\n", wav->format);
- printf( " SubChunk ID: 0x%08x\n", wav->subchkid1);
- printf( " Subchunk1 Size: %u\n", wav->subchklen1);
- printf( " Audio Format: 0x%04x\n", wav->compression);
- printf( " Num. Channels: %d\n", wav->nchannels);
- printf( " Sample Rate: %u\n", wav->samprate);
- printf( " Byte Rate: %u\n", wav->byterate);
- printf( " Block Align: %d\n", wav->align);
- printf( " Bits Per Sample: %d\n", wav->bpsamp);
- printf( " Subchunk2 ID: 0x%08x\n", wav->subchkid2);
- printf( " Subchunk2 Size: %u\n", wav->subchklen2);
+ dbg( "Wave file header\n");
+ dbg( " Chunk ID: 0x%08x\n", wav->hdr.chunkid);
+ dbg( " Chunk Size: %u\n", wav->hdr.chunklen);
+ dbg( " Format: 0x%08x\n", wav->hdr.format);
+ dbg( " SubChunk ID: 0x%08x\n", wav->fmt.chunkid);
+ dbg( " Subchunk1 Size: %u\n", wav->fmt.chunklen);
+ dbg( " Audio Format: 0x%04x\n", wav->fmt.format);
+ dbg( " Num. Channels: %d\n", wav->fmt.nchannels);
+ dbg( " Sample Rate: %u\n", wav->fmt.samprate);
+ dbg( " Byte Rate: %u\n", wav->fmt.byterate);
+ dbg( " Block Align: %d\n", wav->fmt.align);
+ dbg( " Bits Per Sample: %d\n", wav->fmt.bpsamp);
+ dbg( " Subchunk2 ID: 0x%08x\n", wav->data.chunkid);
+ dbg( " Subchunk2 Size: %u\n", wav->data.chunklen);
+}
+#endif
+
+/****************************************************************************
+ * Name: pcm_leuint16
+ *
+ * Description:
+ * Get a 16-bit value stored in little endian order for a big-endian
+ * machine.
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_ENDIAN_BIG
+static uint16_t pcm_leuint16(uint16_t value)
+{
+ return (((value & 0x00ff) << 8) |
+ ((value >> 8) & 0x00ff));
+}
+#endif
+
+/****************************************************************************
+ * Name: pcm_leuint16
+ *
+ * Description:
+ * Get a 16-bit value stored in little endian order for a big-endian
+ * machine.
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_ENDIAN_BIG
+static uint16_t pcm_leuint32(uint32_t value)
+{
+ return (((value & 0x000000ff) << 24) |
+ ((value & 0x0000ff00) << 8) |
+ ((value & 0x00ff0000) >> 8) |
+ ((value & 0xff000000) >> 24));
}
#endif
@@ -213,27 +275,71 @@ static void pcm_dump(FAR const struct wav_header_s *wav)
* Name: pcm_validwav
*
* Description:
- * Return true if this is a valid WAV file header
+ * Return true if this is a valid WAV file header
*
****************************************************************************/
static inline bool pcm_validwav(FAR const struct wav_header_s *wav)
{
- return (wav->chkid == WAV_CHUNKID &&
- wav->format == WAV_FORMAT &&
- wav->subchklen1 == WAV_SUBCHKLEN1);
+ 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->data.chunkid == WAV_DATA_CHUNKID);
+}
+
+/****************************************************************************
+ * Name: pcm_parsewav
+ *
+ * Description:
+ * Parse and verify the WAV file header.
+ *
+ ****************************************************************************/
+
+static bool pcm_parsewav(FAR struct pcm_decode_s *priv, uint8_t *data)
+{
+ FAR const struct wav_header_s *wav = (FAR const struct wav_header_s *)data;
+
+ /* Transfer the purported WAV file header into our private storage,
+ * correcting for endian issues as needed.
+ */
+
+ priv->wav.hdr.chunkid = pcm_leuint32(wav->hdr.chunkid);
+ priv->wav.hdr.chunklen = pcm_leuint32(wav->hdr.chunklen);
+ priv->wav.hdr.format = pcm_leuint32(wav->hdr.format);
+
+ priv->wav.fmt.chunkid = pcm_leuint32(wav->fmt.chunkid);
+ priv->wav.fmt.chunklen = pcm_leuint32(wav->fmt.chunklen);
+ priv->wav.fmt.format = pcm_leuint16(wav->fmt.format);
+ priv->wav.fmt.nchannels = pcm_leuint16(wav->fmt.nchannels);
+ priv->wav.fmt.samprate = pcm_leuint32(wav->fmt.samprate);
+ priv->wav.fmt.byterate = pcm_leuint32(wav->fmt.byterate);
+ priv->wav.fmt.align = pcm_leuint16(wav->fmt.align);
+ priv->wav.fmt.bpsamp = pcm_leuint16(wav->fmt.bpsamp);
+
+ priv->wav.data.chunkid = pcm_leuint32(wav->data.chunkid);
+ priv->wav.data.chunklen = pcm_leuint32(wav->data.chunklen);
+
+ /* Dump the converted wave header information */
+
+ pcm_dump(&priv->wav);
+
+ /* And return true if the the file is a valid WAV header file */
+
+ return pcm_validwav(&priv->wav);
}
/****************************************************************************
* 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.).
+ * 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.).
*
****************************************************************************/
@@ -241,20 +347,48 @@ 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;
-#warning Missing logic
- return -ENOSYS;
+ 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)
+ {
+ auddbg("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)
+ {
+ *((uint16_t *)&caps->ac_format[0]) = (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.
+ * 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.
*
****************************************************************************/
@@ -275,13 +409,13 @@ static int pcm_configure(FAR struct audio_lowerhalf_s *dev,
* 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.
+ * 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.
+ * Any enqueued Audio Pipeline Buffers that have not been processed / dequeued
+ * should be dequeued by this function.
*
****************************************************************************/
@@ -296,10 +430,10 @@ static int pcm_shutdown(FAR struct audio_lowerhalf_s *dev)
* 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.
+ * 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.
*
****************************************************************************/
@@ -318,8 +452,8 @@ static int pcm_start(FAR struct audio_lowerhalf_s *dev)
* Name: pcm_stop
*
* Description:
- * Stop audio streaming and/or processing of enqueued Audio Pipeline
- * Buffers
+ * Stop audio streaming and/or processing of enqueued Audio Pipeline
+ * Buffers
*
****************************************************************************/
@@ -340,8 +474,8 @@ static int pcm_stop(FAR struct audio_lowerhalf_s *dev)
* 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.
+ * 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.
*
****************************************************************************/
@@ -361,7 +495,7 @@ static int pcm_pause(FAR struct audio_lowerhalf_s *dev)
* Name: pcm_resume
*
* Description:
- * Resumes audio streaming after a pause.
+ * Resumes audio streaming after a pause.
*
****************************************************************************/
@@ -381,12 +515,12 @@ static int pcm_resume(FAR struct audio_lowerhalf_s *dev)
* 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 kumalloc using normal user-space
- * memory region.
+ * 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 kumalloc using normal user-space
+ * memory region.
*
****************************************************************************/
@@ -402,6 +536,7 @@ static int pcm_allocbuffer(FAR struct audio_lowerhalf_s *dev,
lower = priv->lower;
DEBUGASSERT(lower && lower->ops->allocbuffer);
+
return lower->ops->allocbuffer(lower, apb);
}
@@ -409,9 +544,9 @@ static int pcm_allocbuffer(FAR struct audio_lowerhalf_s *dev,
* 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.
+ * 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.
*
****************************************************************************/
@@ -427,6 +562,7 @@ static int pcm_freebuffer(FAR struct audio_lowerhalf_s *dev,
lower = priv->lower;
DEBUGASSERT(lower && lower->ops->freebuffer);
+
return lower->ops->freebuffer(lower, apb);
}
@@ -434,16 +570,18 @@ static int pcm_freebuffer(FAR struct audio_lowerhalf_s *dev,
* 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.
+ * 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.
*
****************************************************************************/
@@ -451,15 +589,62 @@ 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;
-#warning Missing logic
- return -ENOSYS;
+ FAR struct audio_lowerhalf_s *lower;
+ apb_samp_t bytesleft;
+
+ DEBUGASSERT(priv);
+ audvdbg("Received buffer %p, streaming=%d\n", apb, priv->streaming);
+
+ lower = priv->lower;
+ DEBUGASSERT(lower && lower->ops->enqueuebuffer);
+
+ /* Are we streaming yet? */
+
+ if (priv->streaming)
+ {
+ /* Yes, just give the buffer to the lower driver */
+
+ 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;
+ audvdbg("curbyte=%d nbytes=%d nmaxbytes=%d bytesleft=%d\n",
+ apb->curbyte, apb->nbytes, apb->nmaxbytes, bytesleft);
+
+ if (bytesleft >= sizeof(struct wav_header_s))
+ {
+ /* Parse and verify the candidate WAV file header */
+
+ if (pcm_parsewav(priv, &apb->samp[apb->curbyte]))
+ {
+ /* Now we are streaming */
+
+ priv->streaming = true;
+
+ /* Bump up the data offset and pass the buffer to the lower level */
+
+ apb->curbyte += sizeof(struct wav_header_s);
+ return lower->ops->enqueuebuffer(lower, apb);
+ }
+ }
+
+ /* This is not a WAV file! */
+
+ auddbg("ERROR: Invalid WAV file\n");
+ return -EINVAL;
}
/****************************************************************************
* Name: pcm_cancelbuffer
*
* Description:
- * Cancel a previously enqueued buffer.
+ * Cancel a previously enqueued buffer.
*
****************************************************************************/
@@ -475,27 +660,35 @@ static int pcm_cancelbuffer(FAR struct audio_lowerhalf_s *dev,
* Name: pcm_ioctl
*
* Description:
- * Lower-half logic may support platform-specific ioctl commands.
+ * Lower-half logic may support platform-specific ioctl commands.
*
****************************************************************************/
-static int pcm_ioctl(FAR struct audio_lowerhalf_s *dev,
- int cmd, unsigned long arg)
+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;
-#warning Missing logic
- return -ENOSYS;
+ FAR struct audio_lowerhalf_s *lower;
+
+ DEBUGASSERT(priv);
+
+ /* Defer the operation to the lower device driver */
+
+ lower = priv->lower;
+ DEBUGASSERT(lower && lower->ops->ioctl);
+
+ 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
+ * 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
*
****************************************************************************/
@@ -506,15 +699,32 @@ static int pcm_reserve(FAR struct audio_lowerhalf_s *dev)
#endif
{
FAR struct pcm_decode_s *priv = (FAR struct pcm_decode_s *)dev;
-#warning Missing logic
- return -ENOSYS;
+ FAR struct audio_lowerhalf_s *lower;
+
+ DEBUGASSERT(priv);
+
+ /* 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).
+ */
+
+ lower = priv->lower;
+ DEBUGASSERT(lower && lower->ops->reserve);
+
+#ifdef CONFIG_AUDIO_MULTI_SESSION
+ return lower->ops->reserve(lower, session);
+#else
+ return lower->ops->reserve(lower);
+#endif
}
/****************************************************************************
* Name: pcm_release
*
* Description:
- * Release a session.
+ * Release a session.
*
****************************************************************************/
@@ -525,8 +735,23 @@ static int pcm_release(FAR struct audio_lowerhalf_s *dev)
#endif
{
FAR struct pcm_decode_s *priv = (FAR struct pcm_decode_s *)dev;
-#warning Missing logic
- return -ENOSYS;
+ 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 wither unless we re-
+ * reserve it).
+ */
+
+ lower = priv->lower;
+ DEBUGASSERT(lower && lower->ops->release);
+
+#ifdef CONFIG_AUDIO_MULTI_SESSION
+ return lower->ops->release(lower, session);
+#else
+ return lower->ops->release(lower);
+#endif
}
/****************************************************************************