From bbd6931d61a3237a82aaae02034dba4f93228d5d Mon Sep 17 00:00:00 2001 From: yaojingwei Date: Mon, 11 Nov 2024 11:23:47 +0800 Subject: [PATCH] nuttx/audio: add fake audio driver. The fake audio driver aims to easily simulate the functions of audio driver through reading and writing files. With it, you can conveniently customize and define various capture or playback audio drivers for debugging or automated testing. Signed-off-by: yaojingwei --- arch/sim/src/sim/sim_initialize.c | 7 + cmake/nuttx_mkconfig.cmake | 1 + drivers/audio/Kconfig | 48 ++ drivers/audio/Make.defs | 4 + drivers/audio/audio_fake.c | 1149 +++++++++++++++++++++++++++++ include/nuttx/audio/audio_fake.h | 52 ++ tools/cfgdefine.c | 1 + 7 files changed, 1262 insertions(+) create mode 100644 drivers/audio/audio_fake.c create mode 100644 include/nuttx/audio/audio_fake.h 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 */