1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468 |
- /****************************************************************************
- * 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/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)
- /****************************************************************************
- * Pre-processor Definitions
- ****************************************************************************/
- /* Configuration ************************************************************/
- #define CONFIG_PCM_DEBUG 1 /* For now */
- /* Often defined and re-defined macros */
- #ifndef MIN
- # define MIN(a,b) (((a) < (b)) ? (a) : (b))
- #endif
- #ifndef MAX
- # define MAX(a,b) (((a) > (b)) ? (a) : (b))
- #endif
- /****************************************************************************
- * 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_PCM_DEBUG
- 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_PCM_DEBUG
- 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.
- *
- ****************************************************************************/
- 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
- *
- ****************************************************************************/
- #ifndef CONFIG_AUDIO_FORMAT_RAW
- 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;
- ssize_t headersize;
- 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
- 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 */
|