* drivers/audio/audio_tunnel.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 <debug.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <nuttx/kmalloc.h>
#include <nuttx/mutex.h>
#include <sys/param.h>
#include <nuttx/audio/audio.h>
#include <nuttx/audio/audio_tunnel.h>
* Private Types
****************************************************************************/
#define AUDIO_TUNNEL_PRODUCER 0
#define AUDIO_TUNNEL_CONSUMER 1
struct audio_tunnel_s;
struct audio_peer_s
{
struct audio_lowerhalf_s dev;
FAR struct audio_tunnel_s *parent;
char devname[16];
struct dq_queue_s pendq;
};
struct audio_tunnel_s
{
mutex_t mutex;
struct audio_info_s info;
struct ap_buffer_info_s binfo;
struct audio_peer_s peers[2];
};
* Private Function Prototypes
****************************************************************************/
static int audio_tunnel_getcaps(FAR struct audio_lowerhalf_s *dev, int type,
FAR struct audio_caps_s *caps);
#ifdef CONFIG_AUDIO_MULTI_SESSION
static int audio_tunnel_reserve(FAR struct audio_lowerhalf_s *dev,
FAR void **session);
static int audio_tunnel_configure(FAR struct audio_lowerhalf_s *dev,
FAR void *session,
FAR const struct audio_caps_s *caps);
static int audio_tunnel_start(FAR struct audio_lowerhalf_s *dev,
FAR void *session);
static int audio_tunnel_release(FAR struct audio_lowerhalf_s *dev,
FAR void *session);
#ifndef CONFIG_AUDIO_EXCLUDE_STOP
static int audio_tunnel_stop(FAR struct audio_lowerhalf_s *dev,
FAR void *session);
#endif
#ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME
static int audio_tunnel_pause(FAR struct audio_lowerhalf_s *dev,
FAR void *session);
static int audio_tunnel_resume(FAR struct audio_lowerhalf_s *dev,
FAR void *session);
#endif
#else
static int audio_tunnel_reserve(FAR struct audio_lowerhalf_s *dev);
static int audio_tunnel_configure(FAR struct audio_lowerhalf_s *dev,
FAR const struct audio_caps_s *caps);
static int audio_tunnel_start(FAR struct audio_lowerhalf_s *dev);
static int audio_tunnel_release(FAR struct audio_lowerhalf_s *dev);
#ifndef CONFIG_AUDIO_EXCLUDE_STOP
static int audio_tunnel_stop(FAR struct audio_lowerhalf_s *dev);
#endif
#ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME
static int audio_tunnel_pause(FAR struct audio_lowerhalf_s *dev);
static int audio_tunnel_resume(FAR struct audio_lowerhalf_s *dev);
#endif
#endif
static int audio_tunnel_shutdown(FAR struct audio_lowerhalf_s *dev);
static int audio_tunnel_enqueuebuffer(FAR struct audio_lowerhalf_s *dev,
FAR struct ap_buffer_s *apb);
static int audio_tunnel_ioctl(FAR struct audio_lowerhalf_s *dev, int cmd,
unsigned long arg);
* Private Data
****************************************************************************/
static const struct audio_ops_s g_audio_tunnel_ops =
{
audio_tunnel_getcaps,
audio_tunnel_configure,
audio_tunnel_shutdown,
audio_tunnel_start,
#ifndef CONFIG_AUDIO_EXCLUDE_STOP
audio_tunnel_stop,
#endif
#ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME
audio_tunnel_pause,
audio_tunnel_resume,
#endif
NULL,
NULL,
audio_tunnel_enqueuebuffer,
NULL,
audio_tunnel_ioctl,
NULL,
NULL,
audio_tunnel_reserve,
audio_tunnel_release
};
* Private Functions
****************************************************************************/
static void audio_tunnel_deliver_buffer(FAR struct audio_tunnel_s *tunnel)
{
FAR struct audio_peer_s *producer =
&tunnel->peers[AUDIO_TUNNEL_PRODUCER];
FAR struct audio_peer_s *consumer =
&tunnel->peers[AUDIO_TUNNEL_CONSUMER];
FAR struct ap_buffer_s *src;
FAR struct ap_buffer_s *dst;
nxmutex_lock(&tunnel->mutex);
if (dq_empty(&producer->pendq) || dq_empty(&consumer->pendq))
{
nxmutex_unlock(&tunnel->mutex);
audinfo("audio tunnel not ready.\n");
return;
}
src = (FAR struct ap_buffer_s *)dq_remfirst(&producer->pendq);
dst = (FAR struct ap_buffer_s *)dq_remfirst(&consumer->pendq);
nxmutex_unlock(&tunnel->mutex);
memcpy(dst->samp, src->samp, src->nbytes);
dst->nbytes = src->nbytes;
#ifdef CONFIG_AUDIO_MULTI_SESSION
consumer->dev.upper(consumer->dev.priv, AUDIO_CALLBACK_DEQUEUE,
dst, OK, NULL);
producer->dev.upper(producer->dev.priv, AUDIO_CALLBACK_DEQUEUE,
src, OK, NULL)
#else
consumer->dev.upper(consumer->dev.priv, AUDIO_CALLBACK_DEQUEUE, dst, OK);
producer->dev.upper(producer->dev.priv, AUDIO_CALLBACK_DEQUEUE, src, OK);
#endif
if (src->flags & AUDIO_APB_FINAL)
{
producer->dev.upper(producer->dev.priv, AUDIO_CALLBACK_COMPLETE,
NULL, OK);
consumer->dev.upper(consumer->dev.priv, AUDIO_CALLBACK_COMPLETE,
NULL, OK);
}
}
static void audio_tunnel_deliver_message(FAR struct audio_peer_s *peer,
int msg_id)
{
FAR struct audio_tunnel_s *tunnel = peer->parent;
struct audio_msg_s msg;
peer = peer == &tunnel->peers[AUDIO_TUNNEL_PRODUCER] ?
&tunnel->peers[AUDIO_TUNNEL_CONSUMER] :
&tunnel->peers[AUDIO_TUNNEL_PRODUCER];
memset(&msg, 0, sizeof(msg));
msg.msg_id = msg_id;
peer->dev.upper(peer->dev.priv, AUDIO_CALLBACK_MESSAGE,
(FAR struct ap_buffer_s *)&msg, OK);
}
* Name: audio_tunnel_getcaps
*
* Description:
* Get the audio capabilities from stub peer.
*
****************************************************************************/
static int audio_tunnel_getcaps(FAR struct audio_lowerhalf_s *dev, int type,
FAR struct audio_caps_s *caps)
{
FAR struct audio_peer_s *peer = (struct audio_peer_s *)dev;
FAR struct audio_tunnel_s *tunnel = peer->parent;
caps->ac_format.hw = 0;
caps->ac_controls.w = 0;
nxmutex_lock(&tunnel->mutex);
switch (caps->ac_type)
{
case AUDIO_TYPE_QUERY:
caps->ac_channels = tunnel->info.channels;
switch (caps->ac_subtype)
{
case AUDIO_TYPE_QUERY:
caps->ac_controls.b[0] =
peer == &tunnel->peers[AUDIO_TUNNEL_PRODUCER] ?
AUDIO_TYPE_OUTPUT : AUDIO_TYPE_INPUT;
if (tunnel->info.subformat == 0)
{
nxmutex_unlock(&tunnel->mutex);
return -ENOSYS;
}
caps->ac_format.hw = 1 << (tunnel->info.format - 1);
break;
case AUDIO_FMT_MP3:
case AUDIO_FMT_PCM:
case AUDIO_FMT_AAC:
case AUDIO_FMT_AC3:
case AUDIO_FMT_AMR:
case AUDIO_FMT_DTS:
case AUDIO_FMT_SBC:
case AUDIO_FMT_WMA:
case AUDIO_FMT_WAV:
case AUDIO_FMT_FLAC:
case AUDIO_FMT_MIDI:
case AUDIO_FMT_MPEG:
case AUDIO_FMT_MSBC:
case AUDIO_FMT_CVSD:
case AUDIO_FMT_OPUS:
case AUDIO_FMT_OGG_VORBIS:
caps->ac_controls.b[0] = tunnel->info.subformat;
caps->ac_controls.b[1] = AUDIO_SUBFMT_END;
memcpy(&caps->ac_codec, &tunnel->info.codec,
sizeof(tunnel->info.codec));
break;
default:
caps->ac_controls.b[0] = AUDIO_SUBFMT_END;
break;
}
break;
case AUDIO_TYPE_OUTPUT:
case AUDIO_TYPE_INPUT:
caps->ac_channels = (tunnel->info.channels << 4) |
(tunnel->info.channels & 0x0f);
switch (caps->ac_subtype)
{
case AUDIO_TYPE_QUERY:
caps->ac_controls.hw[0] =
tunnel->info.samplerate == 8000 ? AUDIO_SAMP_RATE_8K :
tunnel->info.samplerate == 12000 ? AUDIO_SAMP_RATE_12K :
tunnel->info.samplerate == 16000 ? AUDIO_SAMP_RATE_16K :
tunnel->info.samplerate == 22050 ? AUDIO_SAMP_RATE_22K :
tunnel->info.samplerate == 24000 ? AUDIO_SAMP_RATE_24K :
tunnel->info.samplerate == 32000 ? AUDIO_SAMP_RATE_32K :
tunnel->info.samplerate == 44100 ? AUDIO_SAMP_RATE_44K :
tunnel->info.samplerate == 48000 ? AUDIO_SAMP_RATE_48K :
tunnel->info.samplerate == 88200 ? AUDIO_SAMP_RATE_88K :
tunnel->info.samplerate == 96000 ? AUDIO_SAMP_RATE_96K :
tunnel->info.samplerate == 192000 ? AUDIO_SAMP_RATE_192K :
AUDIO_SAMP_RATE_DEF_ALL;
break;
default:
break;
}
break;
default:
caps->ac_subtype = 0;
caps->ac_channels = 0;
break;
}
nxmutex_unlock(&tunnel->mutex);
* proper Audio device type.
*/
audinfo("Return %d\n", caps->ac_len);
return caps->ac_len;
}
* Name: audio_tunnel_configure
*
* Description:
* Configure the driver.
*
****************************************************************************/
#ifdef CONFIG_AUDIO_MULTI_SESSION
static int audio_tunnel_configure(FAR struct audio_lowerhalf_s *dev,
FAR void *session,
FAR const struct audio_caps_s *caps)
#else
static int audio_tunnel_configure(FAR struct audio_lowerhalf_s *dev,
FAR const struct audio_caps_s *caps)
#endif
{
FAR struct audio_peer_s *peer = (struct audio_peer_s *)dev;
FAR struct audio_tunnel_s *tunnel = peer->parent;
audinfo("ac_type: %" PRIu8 "\n", caps->ac_type);
nxmutex_lock(&tunnel->mutex);
switch (caps->ac_type)
{
case AUDIO_TYPE_OUTPUT:
case AUDIO_TYPE_INPUT:
tunnel->info.format = caps->ac_subtype;
tunnel->info.samplerate = caps->ac_controls.hw[0] |
(caps->ac_controls.b[3] << 16);
tunnel->info.channels = caps->ac_channels;
tunnel->info.subformat = caps->ac_format.b[0];
memcpy(&tunnel->info.codec, &caps->ac_codec,
sizeof(caps->ac_codec));
audinfo("Audio type: %s Codec type %" PRIu8 " \
%" PRIu32 " %" PRIu8 " %" PRIu8 "\n",
caps->ac_type == AUDIO_TYPE_OUTPUT ? "OUTPUT" : "INPUT",
caps->ac_subtype,
tunnel->info.samplerate,
tunnel->info.channels,
tunnel->info.subformat);
break;
default:
audinfo("default case: %" PRIu8 "\n", caps->ac_type);
break;
}
nxmutex_unlock(&tunnel->mutex);
return OK;
}
* Name: audio_tunnel_shutdown
*
* Description:
* Shutdown the driver and put it in the lowest power state possible.
*
****************************************************************************/
static int audio_tunnel_shutdown(FAR struct audio_lowerhalf_s *dev)
{
return OK;
}
* Name: audio_tunnel_start
*
* Description:
* Start the driver and put it in the lowest power state possible.
*
****************************************************************************/
#ifdef CONFIG_AUDIO_MULTI_SESSION
static int audio_tunnel_start(FAR struct audio_lowerhalf_s *dev,
FAR void *session)
#else
static int audio_tunnel_start(FAR struct audio_lowerhalf_s *dev)
#endif
{
FAR struct audio_peer_s *peer = (FAR struct audio_peer_s *)dev;
FAR struct audio_tunnel_s *tunnel = peer->parent;
audio_tunnel_deliver_message(peer, AUDIO_MSG_START);
audio_tunnel_deliver_buffer(tunnel);
return OK;
}
* Name: audio_tunnel_stop
*
* Description:
* Stop the configured operation (audio streaming, volume
* disabled, etc.).
*
****************************************************************************/
#ifndef CONFIG_AUDIO_EXCLUDE_STOP
#ifdef CONFIG_AUDIO_MULTI_SESSION
static int audio_tunnel_stop(FAR struct audio_lowerhalf_s *dev,
FAR void *session)
#else
static int audio_tunnel_stop(FAR struct audio_lowerhalf_s *dev)
#endif
{
FAR struct audio_peer_s *peer = (FAR struct audio_peer_s *)dev;
FAR struct audio_tunnel_s *tunnel = peer->parent;
audio_tunnel_deliver_message(peer, AUDIO_MSG_STOP);
nxmutex_lock(&tunnel->mutex);
while (!dq_empty(&peer->pendq))
{
FAR struct ap_buffer_s *apb =
(FAR struct ap_buffer_s *)dq_remfirst(&peer->pendq);
peer->dev.upper(peer->dev.priv, AUDIO_CALLBACK_DEQUEUE, apb, OK);
}
nxmutex_unlock(&tunnel->mutex);
peer->dev.upper(peer->dev.priv, AUDIO_CALLBACK_COMPLETE, NULL, OK);
return OK;
}
#endif
* Name: audio_tunnel_pause
*
* Description: Pauses the playback.
*
****************************************************************************/
#ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME
#ifdef CONFIG_AUDIO_MULTI_SESSION
static int audio_tunnel_pause(FAR struct audio_lowerhalf_s *dev,
FAR void *session)
#else
static int audio_tunnel_pause(FAR struct audio_lowerhalf_s *dev)
#endif
{
FAR struct audio_peer_s *peer = (FAR struct audio_peer_s *)dev;
audio_tunnel_deliver_message(peer, AUDIO_MSG_PAUSE);
return OK;
}
#endif
* Name: audio_tunnel_resume
*
* Description: Resumes the playback.
*
****************************************************************************/
#ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME
#ifdef CONFIG_AUDIO_MULTI_SESSION
static int audio_tunnel_resume(FAR struct audio_lowerhalf_s *dev,
FAR void *session)
#else
static int audio_tunnel_resume(FAR struct audio_lowerhalf_s *dev)
#endif
{
FAR struct audio_peer_s *peer = (FAR struct audio_peer_s *)dev;
FAR struct audio_tunnel_s *tunnel = peer->parent;
audio_tunnel_deliver_message(peer, AUDIO_MSG_RESUME);
audio_tunnel_deliver_buffer(tunnel);
return OK;
}
#endif
* Name: audio_tunnel_enqueuebuffer
*
* Description: Enqueue an Audio Pipeline Buffer for
* playback/capture processing.
*
****************************************************************************/
static int audio_tunnel_enqueuebuffer(FAR struct audio_lowerhalf_s *dev,
FAR struct ap_buffer_s *apb)
{
FAR struct audio_peer_s *peer = (FAR struct audio_peer_s *)dev;
FAR struct audio_tunnel_s *tunnel = peer->parent;
nxmutex_lock(&tunnel->mutex);
dq_addlast(&apb->dq_entry, &peer->pendq);
nxmutex_unlock(&tunnel->mutex);
audio_tunnel_deliver_buffer(tunnel);
return OK;
}
* Name: audio_tunnel_ioctl
*
* Description: Perform a device ioctl
*
****************************************************************************/
static int audio_tunnel_ioctl(FAR struct audio_lowerhalf_s *dev, int cmd,
unsigned long arg)
{
FAR struct audio_peer_s *peer = (FAR struct audio_peer_s *)dev;
FAR struct audio_tunnel_s *tunnel = peer->parent;
FAR struct ap_buffer_info_s *binfo = NULL;
int ret = OK;
audinfo("cmd=%d arg=%ld\n", cmd, arg);
nxmutex_lock(&tunnel->mutex);
switch (cmd)
{
case AUDIOIOC_HWRESET:
audio_tunnel_deliver_message(peer, AUDIO_MSG_IOERR);
break;
case AUDIOIOC_GETBUFFERINFO:
binfo = (FAR struct ap_buffer_info_s *)arg;
binfo->nbuffers = tunnel->binfo.nbuffers;
binfo->buffer_size = tunnel->binfo.buffer_size;
break;
case AUDIOIOC_SETBUFFERINFO:
binfo = (FAR struct ap_buffer_info_s *)arg;
tunnel->binfo.nbuffers = binfo->nbuffers;
tunnel->binfo.buffer_size = binfo->buffer_size;
break;
case AUDIOIOC_GETAUDIOINFO:
memcpy((FAR void *)(uintptr_t)arg, &tunnel->info,
sizeof(tunnel->info));
break;
default:
ret = -ENOTTY;
break;
}
nxmutex_unlock(&tunnel->mutex);
audinfo("Return OK\n");
return ret;
}
#ifdef CONFIG_AUDIO_MULTI_SESSION
static int audio_tunnel_reserve(FAR struct audio_lowerhalf_s *dev,
FAR void **session)
#else
static int audio_tunnel_reserve(FAR struct audio_lowerhalf_s *dev)
#endif
{
return OK;
}
#ifdef CONFIG_AUDIO_MULTI_SESSION
static int audio_tunnel_release(FAR struct audio_lowerhalf_s *dev,
FAR void *session)
#else
static int audio_tunnel_release(FAR struct audio_lowerhalf_s *dev)
#endif
{
return OK;
}
* Public Functions
****************************************************************************/
int audio_tunnel_initialize(FAR const char *prefix)
{
FAR struct audio_tunnel_s *tunnel;
int ret;
int i;
tunnel = (FAR struct audio_tunnel_s *)
kmm_zalloc(sizeof(struct audio_tunnel_s));
if (!tunnel)
{
auderr("ERROR: Failed to allocate tunnel audio device\n");
return -ENOMEM;
}
nxmutex_init(&tunnel->mutex);
for (i = 0; i < nitems(tunnel->peers); i++)
{
snprintf(tunnel->peers[i].devname, sizeof(tunnel->peers[i].devname),
"%s%c", prefix, i == AUDIO_TUNNEL_PRODUCER ? 'p' : 'c');
tunnel->peers[i].dev.ops = &g_audio_tunnel_ops;
tunnel->peers[i].parent = tunnel;
dq_init(&tunnel->peers[i].pendq);
ret = audio_register(tunnel->peers[i].devname, &tunnel->peers[i].dev);
if (ret < 0)
{
goto error;
}
}
return 0;
error:
while (i-- > 0)
{
audio_unregister(tunnel->peers[i].devname, &tunnel->peers[i].dev);
}
nxmutex_destroy(&tunnel->mutex);
kmm_free(tunnel);
return ret;
}