diff --git a/arch/sim/src/sim/sim_initialize.c b/arch/sim/src/sim/sim_initialize.c index b8defe3297..04f02bf2f9 100644 --- a/arch/sim/src/sim/sim_initialize.c +++ b/arch/sim/src/sim/sim_initialize.c @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -298,6 +299,12 @@ void up_initialize(void) audio_register("mixer", sim_audio_initialize(false, false)); +#ifdef CONFIG_AUDIO_FAKE + /* Register fake audio driver */ + + audio_fake_initialize(); +#endif + #endif #ifdef CONFIG_SIM_USB_DEV diff --git a/cmake/nuttx_mkconfig.cmake b/cmake/nuttx_mkconfig.cmake index a9e5d780ff..e5ff721a44 100644 --- a/cmake/nuttx_mkconfig.cmake +++ b/cmake/nuttx_mkconfig.cmake @@ -69,6 +69,7 @@ set(DEQUOTELIST "CONFIG_TTY_LAUNCH_ENTRYPOINT" # Name of entry point from tty launch "CONFIG_TTY_LAUNCH_ARGS" # Argument list of entry point from tty launch "CONFIG_BOARD_MEMORY_RANGE" # Memory range for board + "CONFIG_FAKE_AUDIO_DEVICE_PARAMS" # Arguments for the fake audio device # NxWidgets/NxWM "CONFIG_NXWM_BACKGROUND_IMAGE" # Name of bitmap image class "CONFIG_NXWM_CALIBRATION_ICON" # Name of bitmap image class diff --git a/drivers/audio/Kconfig b/drivers/audio/Kconfig index 8665a215c7..10474cee5f 100644 --- a/drivers/audio/Kconfig +++ b/drivers/audio/Kconfig @@ -518,4 +518,52 @@ config AUDIO_DMA depends on AUDIO depends on DMA +config AUDIO_FAKE + bool "Fake audio device" + default n + depends on AUDIO + ---help--- + A fake audio device driver to simplify testing of audio + capture and playback. + +if AUDIO_FAKE + +config FAKE_AUDIO_DEVICE_PARAMS + string "fake audio device params" + default "" + ---help--- + This is a params for fake audio device. The format like: + {dev_name, playback, sample_rates[4], channels, format[4], period_time, periods}. + + - dev_name (string): pcm2c or pcm2p or others. + - playback (bool): true or false. + - sample_rates(uint32 array): {16000, 32000}. + - channels (uint8, {min_chan, max_chan}): {1, 2}. + - format(uint8 array): {8,16,32}. + - period_time(uint32): 100, unit ms. + - periods (uint32): 4, buffer cnt. + e.g: + {"pcm2c", false, {8000, 16000, 44100}, {1, 2}, {16}, 100, 4}, + {"pcm2p", true, {8000, 16000, 44100}, {1, 2}, {16}, 100, 4}, + +config FAKE_AUDIO_DATA_PATH + string "fake audio device data path" + default "/data/" + ---help--- + This dir is used to store the audio file for virtual capture or playback device. + The filename for capture device should comply with the format "devname_samplerate_channels_format.pcm". + e.g: pcm2c_16000_2_16.pcm. + The filename format of playback device is the same as capture device. + e.g: pcm2p_16000_2_16.pcm. + +config AUDIO_FAKE_MSG_PRIO + int "Fake audio device message priority" + default 1 + +config AUDIO_FAKE_WORKER_STACKSIZE + int "Fake audio device worker thread stack size" + default 768 + +endif # AUDIO_FAKE + endif # DRIVERS_AUDIO diff --git a/drivers/audio/Make.defs b/drivers/audio/Make.defs index 6f0918cbb5..c7a23928dd 100644 --- a/drivers/audio/Make.defs +++ b/drivers/audio/Make.defs @@ -87,6 +87,10 @@ ifeq ($(CONFIG_AUDIO_NULL),y) CSRCS += audio_null.c endif +ifeq ($(CONFIG_AUDIO_FAKE),y) +CSRCS += audio_fake.c +endif + ifeq ($(CONFIG_AUDIO_TONE),y) CSRCS += tone.c endif diff --git a/drivers/audio/audio_fake.c b/drivers/audio/audio_fake.c new file mode 100644 index 0000000000..d6a1f830e1 --- /dev/null +++ b/drivers/audio/audio_fake.c @@ -0,0 +1,1149 @@ +/**************************************************************************** + * drivers/audio/audio_fake.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 + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct audio_fake_params +{ + const char *dev_name; + bool playback; /* True: playback, False: recording */ + uint32_t samplerate[4]; /* Array of sample rate,eg. [44100, 48000, 32000, 22050] */ + uint8_t channels[2]; /* Range of channels, [min_channel, max_channel] */ + uint8_t format[4]; /* Array of format, eg. [8, 16, 32] */ + uint32_t period_time; /* Period time in milliseconds */ + uint32_t periods; /* Number of periods */ +}; + +struct audio_fake_s +{ + struct audio_lowerhalf_s dev; /* Audio lower half (this device) */ + bool playback; /* True: playback, False: recording */ +#ifndef CONFIG_AUDIO_EXCLUDE_STOP + volatile bool terminate; /* True: request to terminate */ +#endif + uint8_t format; /* Request audio format */ + uint32_t channels; /* Request audio channels */ + uint32_t sample_rate; /* Request audio sample rate */ + uint32_t scaler; /* Data bytes to sec scaler (bytes per sec) */ + pthread_t threadid; /* ID of worker thread */ + char mqname[16]; /* Our message queue name */ + struct file mq; /* Message queue for receiving messages */ + struct file file; /* Audio file for playback or capture */ + const struct audio_fake_params *dev_params; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int audio_fake_getcaps(FAR struct audio_lowerhalf_s *dev, int type, + FAR struct audio_caps_s *caps); +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int audio_fake_configure(FAR struct audio_lowerhalf_s *dev, + FAR void *session, + FAR const struct audio_caps_s *caps); +#else +static int audio_fake_configure(FAR struct audio_lowerhalf_s *dev, + FAR const struct audio_caps_s *caps); +#endif +static int audio_fake_shutdown(FAR struct audio_lowerhalf_s *dev); +static void *audio_fake_workerthread(pthread_addr_t pvarg); +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int audio_fake_start(FAR struct audio_lowerhalf_s *dev, + FAR void *session); +#else +static int audio_fake_start(FAR struct audio_lowerhalf_s *dev); +#endif +#ifndef CONFIG_AUDIO_EXCLUDE_STOP +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int audio_fake_stop(FAR struct audio_lowerhalf_s *dev, + FAR void *session); +#else +static int audio_fake_stop(FAR struct audio_lowerhalf_s *dev); +#endif +#endif +#ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int audio_fake_pause(FAR struct audio_lowerhalf_s *dev, + FAR void *session); +static int audio_fake_resume(FAR struct audio_lowerhalf_s *dev, + FAR void *session); +#else +static int audio_fake_pause(FAR struct audio_lowerhalf_s *dev); +static int audio_fake_resume(FAR struct audio_lowerhalf_s *dev); +#endif +#endif +static int audio_fake_enqueuebuffer(FAR struct audio_lowerhalf_s *dev, + FAR struct ap_buffer_s *apb); +static int audio_fake_cancelbuffer(FAR struct audio_lowerhalf_s *dev, + FAR struct ap_buffer_s *apb); +static int audio_fake_ioctl(FAR struct audio_lowerhalf_s *dev, int cmd, + unsigned long arg); +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int audio_fake_reserve(FAR struct audio_lowerhalf_s *dev, + FAR void **session); +#else +static int audio_fake_reserve(FAR struct audio_lowerhalf_s *dev); +#endif +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int audio_fake_release(FAR struct audio_lowerhalf_s *dev, + FAR void *session); +#else +static int audio_fake_release(FAR struct audio_lowerhalf_s *dev); +#endif +static int audio_fake_file_init(FAR struct audio_lowerhalf_s *dev); +static int audio_fake_file_deinit(FAR struct audio_lowerhalf_s *dev); +static int audio_fake_file_write(FAR struct audio_lowerhalf_s *dev, + FAR struct ap_buffer_s *apb); +static int audio_fake_file_read(FAR struct audio_lowerhalf_s *dev, + FAR struct ap_buffer_s *apb); +static int audio_fake_process_buffer(FAR struct audio_lowerhalf_s *dev, + FAR struct ap_buffer_s *apb); +static uint8_t AUDIO_SUBFMT_CONVERT(uint8_t format); +static uint32_t AUDIO_SAMP_RATE_CONVERT(uint32_t samplerate); +static FAR struct audio_lowerhalf_s * +audio_fake_init_device(bool playback, + FAR const struct audio_fake_params *dev_params); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct audio_fake_params g_dev_params[] = +{ +#ifdef CONFIG_FAKE_AUDIO_DEVICE_PARAMS + CONFIG_FAKE_AUDIO_DEVICE_PARAMS +#endif +}; + +static const struct audio_ops_s g_audioops = +{ + audio_fake_getcaps, /* getcaps */ + audio_fake_configure, /* configure */ + audio_fake_shutdown, /* shutdown */ + audio_fake_start, /* start */ +#ifndef CONFIG_AUDIO_EXCLUDE_STOP + audio_fake_stop, /* stop */ +#endif +#ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME + audio_fake_pause, /* pause */ + audio_fake_resume, /* resume */ +#endif + NULL, /* allocbuffer */ + NULL, /* freebuffer */ + audio_fake_enqueuebuffer, /* enqueue_buffer */ + audio_fake_cancelbuffer, /* cancel_buffer */ + audio_fake_ioctl, /* ioctl */ + NULL, /* read */ + NULL, /* write */ + audio_fake_reserve, /* reserve */ + audio_fake_release /* release */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: AUDIO_SAMP_RATE_CONVERT + * + * Description: Convert the samplerate to Nuttx audio samplerate. + * + ****************************************************************************/ + +static uint32_t AUDIO_SAMP_RATE_CONVERT(uint32_t samplerate) +{ + if (samplerate == 8000) + { + return AUDIO_SAMP_RATE_8K; + } + else if (samplerate == 11025) + { + return AUDIO_SAMP_RATE_11K; + } + else if (samplerate == 16000) + { + return AUDIO_SAMP_RATE_16K; + } + else if (samplerate == 22050) + { + return AUDIO_SAMP_RATE_22K; + } + else if (samplerate == 32000) + { + return AUDIO_SAMP_RATE_32K; + } + else if (samplerate == 44100) + { + return AUDIO_SAMP_RATE_44K; + } + else if (samplerate == 48000) + { + return AUDIO_SAMP_RATE_48K; + } + else if (samplerate != 0) + { + auderr("ERROR: Unsupported sample rate %d\n", samplerate); + } + + return 0; +} + +/**************************************************************************** + * Name: AUDIO_SUBFMT_CONVERT + * + * Description: Convert the format to Nuttx audio format. + * + ****************************************************************************/ + +static uint8_t AUDIO_SUBFMT_CONVERT(uint8_t format) +{ + if (format == 8) + { + return AUDIO_SUBFMT_PCM_S8; + } + else if (format == 16) + { + return AUDIO_SUBFMT_PCM_S16_LE; + } + else if (format == 32) + { + return AUDIO_SUBFMT_PCM_S32_LE; + } + else if (format != AUDIO_SUBFMT_END) + { + auderr("ERROR: Unsupported format %d\n", format); + } + + return 0; +} + +/**************************************************************************** + * Name: audio_fake_file_init + * + * Description: Initialize the audio file for playback or capture virtual + * audio driver. + * + ****************************************************************************/ + +static int audio_fake_file_init(FAR struct audio_lowerhalf_s *dev) +{ + FAR struct audio_fake_s *priv = (FAR struct audio_fake_s *)dev; + char filename[64]; + int ret; + + snprintf(filename, sizeof(filename), "%s/%s_%d_%d_%d.pcm", + CONFIG_FAKE_AUDIO_DATA_PATH, priv->dev_params->dev_name, + priv->sample_rate, priv->channels, priv->format); + + if (priv->playback) + { + ret = file_open(&priv->file, filename, O_RDWR | O_CREAT | O_CLOEXEC, + 0666); + } + else + { + ret = file_open(&priv->file, filename, O_RDONLY | O_CLOEXEC); + } + + audwarn("open %s file %s\n", filename, (ret < 0) ? "fail" : "success"); + + return ret; +} + +/**************************************************************************** + * Name: audio_fake_file_deinit + * + * Description: Deinitialize the audio file. + * + ****************************************************************************/ + +static int audio_fake_file_deinit(FAR struct audio_lowerhalf_s *dev) +{ + FAR struct audio_fake_s *priv = (FAR struct audio_fake_s *)dev; + + audinfo("audio_fake_file_deinit, close file\n"); + file_close(&priv->file); + + return 0; +} + +/**************************************************************************** + * Name: audio_fake_file_write + * + * Description: Write the audio data to file. + * + ****************************************************************************/ + +static int audio_fake_file_write(FAR struct audio_lowerhalf_s *dev, + FAR struct ap_buffer_s *apb) +{ + FAR struct audio_fake_s *priv = (FAR struct audio_fake_s *)dev; + int ret; + + ret = file_write(&priv->file, apb->samp, apb->nbytes); + if (ret < 0) + { + auderr("Error write data , ret %d\n", ret); + return ret; + } + + return ret; +} + +/**************************************************************************** + * Name: audio_fake_file_read + * + * Description: Read the audio data from file. + * + ****************************************************************************/ + +static int audio_fake_file_read(FAR struct audio_lowerhalf_s *dev, + FAR struct ap_buffer_s *apb) +{ + FAR struct audio_fake_s *priv = (FAR struct audio_fake_s *)dev; + int ret; + + ret = file_read(&priv->file, apb->samp, apb->nmaxbytes); + if (ret == 0) + { + audwarn("read file end\n"); + file_seek(&priv->file, 0, SEEK_SET); + ret = file_read(&priv->file, apb->samp, apb->nmaxbytes); + } + + if (ret < 0) + { + auderr("Error read data , ret %d\n", ret); + return ret; + } + + apb->nbytes = ret; + apb->curbyte = 0; + apb->flags = 0; + + return ret; +} + +/**************************************************************************** + * Name: audio_fake_process_buffer + * + * Description: Process the audio buffer. + * + ****************************************************************************/ + +static int audio_fake_process_buffer(FAR struct audio_lowerhalf_s *dev, + FAR struct ap_buffer_s *apb) +{ + FAR struct audio_fake_s *priv = (FAR struct audio_fake_s *)dev; + int64_t frame_time; + int64_t diff_time; + int64_t sleep_time; + struct timeval tv1; + struct timeval tv2; + int ret; + + audinfo("process apb=%p, nbytes=%d\n", apb, apb->nbytes); + + /* Check if this was the last buffer in the stream */ + + priv->terminate = ((apb->flags & AUDIO_APB_FINAL) != 0); + + gettimeofday(&tv1, NULL); + + if (priv->playback) + { + ret = audio_fake_file_write(dev, apb); + if (ret < 0) + { + auderr("Error write data, ret %d\n", ret); + goto out; + } + } + else + { + ret = audio_fake_file_read(dev, apb); + if (ret < 0) + { + auderr("Error read data , ret %d\n", ret); + goto out; + } + } + + gettimeofday(&tv2, NULL); + + frame_time = ((int64_t)apb->nbytes * 1000 * 1000) / priv->scaler; + + diff_time = (int64_t)tv2.tv_sec * 1000000 + tv2.tv_usec - + ((int64_t)tv1.tv_sec * 1000000 + tv1.tv_usec); + + if (diff_time >= frame_time) + { + audwarn("WARN: %s file time %" PRId64 " > frame time %" PRId64 ".\n", + priv->playback ? "write" : "read", + diff_time, frame_time); + ret = OK; + goto out; + } + + sleep_time = frame_time - diff_time; + + nxsig_usleep(sleep_time); + + ret = OK; + +out: +#ifdef CONFIG_AUDIO_MULTI_SESSION + priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_DEQUEUE, apb, OK, NULL); +#else + priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_DEQUEUE, apb, OK); +#endif + + return ret; +} + +/**************************************************************************** + * Name: audio_fake_getcaps + * + * Description: Get the audio device capabilities + * + ****************************************************************************/ + +static int audio_fake_getcaps(FAR struct audio_lowerhalf_s *dev, int type, + FAR struct audio_caps_s *caps) +{ + FAR struct audio_fake_s *priv = (struct audio_fake_s *)dev; + + audinfo("type=%d\n", type); + + /* Validate the structure */ + + DEBUGASSERT(caps->ac_len >= sizeof(struct audio_caps_s)); + + /* Fill in the caller's structure based on requested info */ + + caps->ac_format.hw = 0; + caps->ac_controls.w = 0; + + switch (caps->ac_type) + { + /* Caller is querying for the types of units we support */ + + case AUDIO_TYPE_QUERY: + + /* Provide our overall capabilities. The interfacing software + * must then call us back for specific info for each capability. + */ + + caps->ac_channels = (priv->dev_params->channels[0] << 4) | + (priv->dev_params->channels[1] & 0x0f); + + switch (caps->ac_subtype) + { + case AUDIO_TYPE_QUERY: + /* We don't decode any formats! Only something above us in + * the audio stream can perform decoding on our behalf. + */ + + /* The types of audio units we implement */ + + caps->ac_controls.b[0] = + priv->playback ? AUDIO_TYPE_OUTPUT : AUDIO_TYPE_INPUT; + caps->ac_format.hw = 1 << (AUDIO_FMT_PCM - 1); + + break; + + case AUDIO_FMT_PCM: + + caps->ac_controls.b[0] = + AUDIO_SUBFMT_CONVERT(priv->dev_params->format[0]); + caps->ac_controls.b[1] = + AUDIO_SUBFMT_CONVERT(priv->dev_params->format[1]); + caps->ac_controls.b[2] = + AUDIO_SUBFMT_CONVERT(priv->dev_params->format[2]); + caps->ac_controls.b[3] = + AUDIO_SUBFMT_CONVERT(priv->dev_params->format[3]); + + break; + + default: + caps->ac_controls.b[0] = AUDIO_SUBFMT_END; + break; + } + + break; + + /* Provide capabilities of our OUTPUT unit */ + + case AUDIO_TYPE_OUTPUT: + case AUDIO_TYPE_INPUT: + + caps->ac_channels = (priv->dev_params->channels[0] << 4) | + (priv->dev_params->channels[1] & 0x0f); + + switch (caps->ac_subtype) + { + case AUDIO_TYPE_QUERY: + + caps->ac_channels = (priv->dev_params->channels[0] << 4) | + (priv->dev_params->channels[1] & 0x0f); + + /* Report the Sample rates we support */ + + caps->ac_controls.hw[0] = + AUDIO_SAMP_RATE_CONVERT(priv->dev_params->samplerate[0]) | + AUDIO_SAMP_RATE_CONVERT(priv->dev_params->samplerate[1]) | + AUDIO_SAMP_RATE_CONVERT(priv->dev_params->samplerate[2]) | + AUDIO_SAMP_RATE_CONVERT(priv->dev_params->samplerate[3]); + + break; + + default: + break; + } + + break; + + /* All others we don't support */ + + default: + + /* Zero out the fields to indicate no support */ + + caps->ac_subtype = 0; + caps->ac_channels = 0; + + break; + } + + /* Return the length of the audio_caps_s struct for validation of + * proper Audio device type. + */ + + audinfo("Return %d\n", caps->ac_len); + return caps->ac_len; +} + +/**************************************************************************** + * Name: audio_fake_configure + * + * Description: + * Configure the audio device for the specified mode of operation. + * + ****************************************************************************/ + +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int audio_fake_configure(FAR struct audio_lowerhalf_s *dev, + FAR void *session, + FAR const struct audio_caps_s *caps) +#else +static int audio_fake_configure(FAR struct audio_lowerhalf_s *dev, + FAR const struct audio_caps_s *caps) +#endif +{ + FAR struct audio_fake_s *priv = (FAR struct audio_fake_s *)dev; + int ret; + audinfo("ac_type: %d\n", caps->ac_type); + + if (priv->mqname[0] == '\0') + { + struct mq_attr attr; + + /* Create a message queue for the worker thread */ + + snprintf(priv->mqname, sizeof(priv->mqname), "/tmp/%" PRIXPTR, + (uintptr_t)priv); + + attr.mq_maxmsg = 16; + attr.mq_msgsize = sizeof(struct audio_msg_s); + attr.mq_curmsgs = 0; + attr.mq_flags = 0; + + ret = file_mq_open(&priv->mq, priv->mqname, O_RDWR | O_CREAT, 0644, + &attr); + if (ret < 0) + { + /* Error creating message queue! */ + + auderr("ERROR: Couldn't allocate message queue\n"); + return ret; + } + } + + /* Process the configure operation */ + + switch (caps->ac_type) + { + case AUDIO_TYPE_OUTPUT: + case AUDIO_TYPE_INPUT: + priv->sample_rate = + caps->ac_controls.hw[0] | (caps->ac_controls.b[3] << 16); + priv->channels = caps->ac_channels; + priv->format = caps->ac_controls.b[2]; + + priv->scaler = caps->ac_channels * caps->ac_controls.hw[0] * + caps->ac_controls.b[2] / 8; + + audinfo("Audio type: %s\n", (caps->ac_type == AUDIO_TYPE_OUTPUT) + ? "AUDIO_TYPE_OUTPUT" + : "AUDIO_TYPE_INPUT"); + audinfo("Number of channels: %u\n", caps->ac_channels); + audinfo("Sample rate: %u\n", caps->ac_controls.hw[0]); + audinfo("Sample width: %u\n", caps->ac_controls.b[2]); + break; + + default: + audinfo("default case: %d\n", caps->ac_type); + break; + } + + audinfo("Return OK\n"); + return OK; +} + +/**************************************************************************** + * Name: audio_fake_shutdown + * + * Description: + * Shutdown the driver and put it in the lowest power state possible. + * + ****************************************************************************/ + +static int audio_fake_shutdown(FAR struct audio_lowerhalf_s *dev) +{ + audinfo("Return OK\n"); + return OK; +} + +/**************************************************************************** + * Name: audio_fake_workerthread + * + * This is the thread that feeds data to the chip and keeps the audio + * stream going. + * + ****************************************************************************/ + +static void *audio_fake_workerthread(pthread_addr_t pvarg) +{ + FAR struct audio_fake_s *priv = (FAR struct audio_fake_s *)pvarg; + FAR struct ap_buffer_s *apb; + struct audio_msg_s msg; + struct mq_attr attr; + unsigned int prio; + int msglen; + int ret; + + audinfo("Entry\n"); + + /* Loop as long as we are supposed to be running */ + +#ifndef CONFIG_AUDIO_EXCLUDE_STOP + while (!priv->terminate) +#else + for (; ; ) +#endif + { + /* Wait for messages from our message queue */ + + msglen = + file_mq_receive(&priv->mq, (FAR char *)&msg, sizeof(msg), &prio); + + /* Handle the case when we return with no message */ + + if (msglen < sizeof(struct audio_msg_s)) + { + auderr("ERROR: Message too small: %d\n", msglen); + continue; + } + + /* Process the message */ + + switch (msg.msg_id) + { + case AUDIO_MSG_DATA_REQUEST: + break; + +#ifndef CONFIG_AUDIO_EXCLUDE_STOP + case AUDIO_MSG_STOP: + + /* Consume all buffers on the bufferq after stop */ + + for (; ; ) + { + file_mq_getattr(&priv->mq, &attr); + if (attr.mq_curmsgs > 0) + { + file_mq_receive(&priv->mq, (FAR char *)&msg, sizeof(msg), + &prio); + + /* direct dequeue buffer to application */ + + apb = (FAR struct ap_buffer_s *)msg.u.ptr; +#ifdef CONFIG_AUDIO_MULTI_SESSION + priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_DEQUEUE, + apb, OK, NULL); +#else + priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_DEQUEUE, + apb, OK); +#endif + continue; + } + else + { + break; + } + } + + priv->terminate = true; + break; +#endif + + case AUDIO_MSG_ENQUEUE: + apb = (FAR struct ap_buffer_s *)msg.u.ptr; + ret = audio_fake_process_buffer(&priv->dev, apb); + if (ret) + { + auderr("fake audio process error %d\n", ret); + priv->terminate = true; + } + break; + + default: + auderr("ERROR: Ignoring message ID %d\n", msg.msg_id); + break; + } + } + + /* Close the message queue */ + + file_mq_close(&priv->mq); + file_mq_unlink(priv->mqname); + priv->mqname[0] = '\0'; + priv->terminate = false; + + audio_fake_file_deinit(&priv->dev); + +#ifdef CONFIG_AUDIO_MULTI_SESSION + priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_COMPLETE, NULL, OK, NULL); +#else + priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_COMPLETE, NULL, OK); +#endif + audinfo("Exit %s\n", priv->dev_params->dev_name); + + return NULL; +} + +/**************************************************************************** + * Name: audio_fake_start + * + * Description: + * Start the configured operation (audio streaming, volume enabled, etc.). + * + ****************************************************************************/ + +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int audio_fake_start(FAR struct audio_lowerhalf_s *dev, + FAR void *session) +#else +static int audio_fake_start(FAR struct audio_lowerhalf_s *dev) +#endif +{ + FAR struct audio_fake_s *priv = (FAR struct audio_fake_s *)dev; + struct sched_param sparam; + pthread_attr_t tattr; + FAR void *value; + int ret; + + priv->terminate = false; + + ret = audio_fake_file_init(dev); + if (ret < 0) + { + return ret; + } + + /* Join any old worker thread we had created to prevent a memory leak */ + + if (priv->threadid != 0) + { + audinfo("Joining old thread\n"); + pthread_join(priv->threadid, &value); + } + + /* Start our thread for sending data to the device */ + + pthread_attr_init(&tattr); + sparam.sched_priority = sched_get_priority_max(SCHED_FIFO) - 3; + pthread_attr_setschedparam(&tattr, &sparam); + pthread_attr_setstacksize(&tattr, CONFIG_AUDIO_FAKE_WORKER_STACKSIZE); + + audinfo("Starting worker thread\n"); + ret = pthread_create(&priv->threadid, &tattr, audio_fake_workerthread, + (pthread_addr_t)priv); + if (ret != OK) + { + auderr("ERROR: pthread_create failed: %d\n", ret); + } + else + { + pthread_setname_np(priv->threadid, "audio_fake"); + audinfo("Created worker thread\n"); + } + + audinfo("Return %d\n", ret); + return ret; +} + +/**************************************************************************** + * Name: audio_fake_stop + * + * Description: Stop the configured operation (audio streaming, volume + * disabled, etc.). + * + ****************************************************************************/ + +#ifndef CONFIG_AUDIO_EXCLUDE_STOP +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int audio_fake_stop(FAR struct audio_lowerhalf_s *dev, + FAR void *session) +#else +static int audio_fake_stop(FAR struct audio_lowerhalf_s *dev) +#endif +{ + FAR struct audio_fake_s *priv = (FAR struct audio_fake_s *)dev; + struct audio_msg_s term_msg; + FAR void *value; + + /* Send a message to stop all audio streaming */ + + term_msg.msg_id = AUDIO_MSG_STOP; + term_msg.u.data = 0; + file_mq_send(&priv->mq, (FAR const char *)&term_msg, sizeof(term_msg), + CONFIG_AUDIO_FAKE_MSG_PRIO); + + /* Join the worker thread */ + + pthread_join(priv->threadid, &value); + priv->threadid = 0; + +#ifdef CONFIG_AUDIO_MULTI_SESSION + dev->upper(dev->priv, AUDIO_CALLBACK_COMPLETE, NULL, OK, NULL); +#else + dev->upper(dev->priv, AUDIO_CALLBACK_COMPLETE, NULL, OK); +#endif + + audinfo("Return OK\n"); + return OK; +} +#endif + +/**************************************************************************** + * Name: audio_fake_pause + * + * Description: Pauses the playback. + * + ****************************************************************************/ + +#ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int audio_fake_pause(FAR struct audio_lowerhalf_s *dev, + FAR void *session) +#else +static int audio_fake_pause(FAR struct audio_lowerhalf_s *dev) +#endif +{ + FAR struct audio_fake_s *priv = (FAR struct audio_fake_s *)dev; + audinfo("%s pause\n", priv->dev_params->dev_name); + return OK; +} +#endif /* CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME */ + +/**************************************************************************** + * Name: audio_fake_resume + * + * Description: Resumes the playback. + * + ****************************************************************************/ + +#ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int audio_fake_resume(FAR struct audio_lowerhalf_s *dev, + FAR void *session) +#else +static int audio_fake_resume(FAR struct audio_lowerhalf_s *dev) +#endif +{ + FAR struct audio_fake_s *priv = (FAR struct audio_fake_s *)dev; + audinfo("%s resume\n", priv->dev_params->dev_name); + return OK; +} +#endif /* CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME */ + +/**************************************************************************** + * Name: audio_fake_enqueuebuffer + * + * Description: Enqueue an Audio Pipeline Buffer for + * playback/capture processing. + * + ****************************************************************************/ + +static int audio_fake_enqueuebuffer(FAR struct audio_lowerhalf_s *dev, + FAR struct ap_buffer_s *apb) +{ + FAR struct audio_fake_s *priv = (FAR struct audio_fake_s *)dev; + struct audio_msg_s msg; + int ret; + + DEBUGASSERT(priv && apb && priv->dev.upper); + + audinfo("apb=%p curbyte=%d nbytes=%d\n", apb, apb->curbyte, apb->nbytes); + + msg.msg_id = AUDIO_MSG_ENQUEUE; + msg.u.ptr = apb; + + ret = file_mq_send(&priv->mq, (FAR const char *)&msg, sizeof(msg), + CONFIG_AUDIO_FAKE_MSG_PRIO); + if (ret < 0) + { + auderr("ERROR: file_mq_send failed: %d\n", ret); + } + + audinfo("Return OK\n"); + return ret; +} + +/**************************************************************************** + * Name: audio_fake_cancelbuffer + * + * Description: Called when an enqueued buffer is being cancelled. + * + ****************************************************************************/ + +static int audio_fake_cancelbuffer(FAR struct audio_lowerhalf_s *dev, + FAR struct ap_buffer_s *apb) +{ + audinfo("apb=%p curbyte=%d nbytes=%d, return OK\n", apb, apb->curbyte, + apb->nbytes); + + return OK; +} + +/**************************************************************************** + * Name: audio_fake_ioctl + * + * Description: Perform a device ioctl + * + ****************************************************************************/ + +static int audio_fake_ioctl(FAR struct audio_lowerhalf_s *dev, int cmd, + unsigned long arg) +{ + FAR struct audio_fake_s *priv = (FAR struct audio_fake_s *)dev; + int ret = OK; +#ifdef CONFIG_AUDIO_DRIVER_SPECIFIC_BUFFERS + FAR struct ap_buffer_info_s *bufinfo; +#endif + + audinfo("cmd=%d arg=%ld\n", cmd, arg); + + /* Deal with ioctls passed from the upper-half driver */ + + switch (cmd) + { + /* Check for AUDIOIOC_HWRESET ioctl. This ioctl is passed straight + * through from the upper-half audio driver. + */ + + case AUDIOIOC_HWRESET: + { + audinfo("AUDIOIOC_HWRESET:\n"); + } + break; + + /* Report our preferred buffer size and quantity */ + + case AUDIOIOC_GETBUFFERINFO: + { + audinfo("AUDIOIOC_GETBUFFERINFO:\n"); +#ifdef CONFIG_AUDIO_DRIVER_SPECIFIC_BUFFERS + bufinfo = (FAR struct ap_buffer_info_s *)arg; + bufinfo->buffer_size = + (priv->scaler * priv->dev_params->period_time) / 1000; + bufinfo->nbuffers = priv->dev_params->periods; +#else + audwarn("AUDIOIOC_GETBUFFERINFO Return EPERM\n"); + return -EPERM; +#endif + } + break; + default: + ret = -ENOTTY; + break; + } + + audinfo("Return OK\n"); + return ret; +} + +/**************************************************************************** + * Name: audio_fake_reserve + * + * Description: Reserves a session (the only one we have). + * + ****************************************************************************/ + +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int audio_fake_reserve(FAR struct audio_lowerhalf_s *dev, + FAR void **session) +#else +static int audio_fake_reserve(FAR struct audio_lowerhalf_s *dev) +#endif +{ + FAR struct audio_fake_s *priv = (FAR struct audio_fake_s *)dev; + priv->terminate = false; + audinfo("%s reserve\n", priv->dev_params->dev_name); + return OK; +} + +/**************************************************************************** + * Name: audio_fake_release + * + * Description: Releases the session (the only one we have). + * + ****************************************************************************/ + +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int audio_fake_release(FAR struct audio_lowerhalf_s *dev, + FAR void *session) +#else +static int audio_fake_release(FAR struct audio_lowerhalf_s *dev) +#endif +{ + FAR struct audio_fake_s *priv = (FAR struct audio_fake_s *)dev; + void *value; + + /* Join any old worker thread we had created to prevent a memory leak */ + + if (priv->threadid != 0) + { + pthread_join(priv->threadid, &value); + priv->threadid = 0; + } + + return OK; +} + +/**************************************************************************** + * Name: audio_fake_init_device + * + * Description: Initialize the audio device. + * + ****************************************************************************/ + +static FAR struct audio_lowerhalf_s * +audio_fake_init_device(bool playback, + FAR const struct audio_fake_params *dev_params) +{ + FAR struct audio_fake_s *priv; + + /* Allocate the fake audio device structure */ + + priv = (FAR struct audio_fake_s *)kmm_zalloc(sizeof(struct audio_fake_s)); + if (!priv) + { + auderr("ERROR: Failed to allocate fake audio device\n"); + return NULL; + } + + priv->dev.ops = &g_audioops; + priv->dev_params = dev_params; + priv->playback = playback; + priv->terminate = false; + + return &priv->dev; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: audio_fake_initialize + * + * Description: + * Initialize and register fake audio device. + * + * Returned Value: + * 0 is returned on success; + * others is returned on failure. + * + ****************************************************************************/ + +int audio_fake_initialize(void) +{ + int ret; + int i; + + for (i = 0; i < nitems(g_dev_params); i++) + { + ret = audio_register(g_dev_params[i].dev_name, + audio_fake_init_device(g_dev_params[i].playback, + &g_dev_params[i])); + if (ret < 0) + { + auderr("ERROR: Failed to register (%s) fake audio device.\n", + g_dev_params[i].dev_name); + return ret; + } + } + + return 0; +} diff --git a/include/nuttx/audio/audio_fake.h b/include/nuttx/audio/audio_fake.h new file mode 100644 index 0000000000..e030abcdde --- /dev/null +++ b/include/nuttx/audio/audio_fake.h @@ -0,0 +1,52 @@ +/**************************************************************************** + * include/nuttx/audio/audio_fake.h + * + * 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. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_AUDIO_AUDIO_FAKE_H +#define __INCLUDE_NUTTX_AUDIO_AUDIO_FAKE_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#ifdef CONFIG_AUDIO_FAKE + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +int audio_fake_initialize(void); + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* CONFIG_AUDIO_FAKE */ +#endif /* __INCLUDE_NUTTX_AUDIO_AUDIO_FAKE_H */ diff --git a/tools/cfgdefine.c b/tools/cfgdefine.c index 575aeb9f85..f50863aecf 100644 --- a/tools/cfgdefine.c +++ b/tools/cfgdefine.c @@ -65,6 +65,7 @@ static const char *dequote_list[] = "CONFIG_TTY_LAUNCH_ENTRYPOINT", /* Name of entry point from tty launch */ "CONFIG_TTY_LAUNCH_ARGS", /* Argument list of entry point from tty launch */ "CONFIG_BOARD_MEMORY_RANGE", /* Memory range for board */ + "CONFIG_FAKE_AUDIO_DEVICE_PARAMS", /* Arguments for the fake audio device */ /* NxWidgets/NxWM */