diff --git a/arch/sim/src/sim/sim_initialize.c b/arch/sim/src/sim/sim_initialize.c index b8defe329723b..a85955ed5cdd6 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)); +#if defined(CONFIG_AUDIO_NULL) && defined(CONFIG_AUDIO_NULL_DEVICE_PARAMS) + /* Register null audio driver */ + + null_audio_register(); +#endif + #endif #ifdef CONFIG_SIM_USB_DEV diff --git a/cmake/nuttx_mkconfig.cmake b/cmake/nuttx_mkconfig.cmake index a9e5d780ff80b..7f09ab31df816 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_AUDIO_NULL_DEVICE_PARAMS" # Arguments for the null 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 8665a215c75dd..f4942b9e439e4 100644 --- a/drivers/audio/Kconfig +++ b/drivers/audio/Kconfig @@ -505,6 +505,33 @@ config AUDIO_NULL_WORKER_STACKSIZE int "Null audio device worker thread stack size" default 768 +config AUDIO_NULL_DEVICE_PARAMS + string "Null audio device params" + default "" + ---help--- + This is a params for null audio device. The params is following format: + {dev_name, mode, sample_rates[4], channels, format[4], period_time, periods}. + + - dev_name (string): pcm2c or pcm2p or others. + - mode (string): playback or capture. + - 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", "capture", {8000, 16000, 44100}, {1, 2}, {16}, 100, 4}, + {"pcm2p", "playback", {8000, 16000, 44100}, {1, 2}, {16}, 100, 4}, + +config AUDIO_NULL_DATA_PATH + string "Null 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. endif # AUDIO_NULL config AUDIO_I2S diff --git a/drivers/audio/audio_null.c b/drivers/audio/audio_null.c index 83a4633986c16..1dbdbf2977fc2 100644 --- a/drivers/audio/audio_null.c +++ b/drivers/audio/audio_null.c @@ -45,15 +45,31 @@ #include #include #include +#include +#include +#include /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ +#define ARRAY_ELEMS(a) (sizeof(a) / sizeof((a)[0])) + /**************************************************************************** * Private Types ****************************************************************************/ +struct null_dev_params_s +{ + const char *dev_name; + const char *mode; + uint32_t samplerate[4]; + uint8_t channels[2]; + uint8_t format[4]; + uint32_t period_time; + uint32_t periods; +}; + struct null_dev_s { struct audio_lowerhalf_s dev; /* Audio lower half (this device) */ @@ -65,6 +81,16 @@ struct null_dev_s #ifndef CONFIG_AUDIO_EXCLUDE_STOP volatile bool terminate; /* True: request to terminate */ #endif + bool config_params; /* Init null audio driver with config params */ + bool paused; + bool started; + uint8_t format; + uint32_t channels; + uint32_t sample_rate; + timer_t timer_id; + struct file file; + struct dq_queue_s pendq; + const struct null_dev_params_s *dev_params; }; /**************************************************************************** @@ -128,11 +154,33 @@ static int null_release(FAR struct audio_lowerhalf_s *dev); #endif static int null_sleep(FAR struct audio_lowerhalf_s *dev, FAR struct ap_buffer_s *apb); +static int null_file_init(FAR struct audio_lowerhalf_s *dev); +static int null_file_deinit(FAR struct audio_lowerhalf_s *dev); +static int null_file_write(FAR struct audio_lowerhalf_s *dev, + FAR struct ap_buffer_s *apb); +static int null_file_read(FAR struct audio_lowerhalf_s *dev, + FAR struct ap_buffer_s *apb); +static int null_process_buffer(FAR struct audio_lowerhalf_s *dev, + FAR struct ap_buffer_s *apb); +static uint8_t null_convert_format(uint8_t format); +static uint32_t null_convert_samplerate(uint32_t samplerate); +static FAR struct audio_lowerhalf_s * +null_init_device(bool playback, FAR struct null_dev_params_s *dev_params); +static void null_capture_data_callback(union sigval value); +static int null_capture_create_timer(FAR struct audio_lowerhalf_s *dev); +static int null_capture_delete_timer(FAR struct audio_lowerhalf_s *dev); /**************************************************************************** * Private Data ****************************************************************************/ +static struct null_dev_params_s g_dev_params[] = +{ +#ifdef CONFIG_AUDIO_NULL_DEVICE_PARAMS + CONFIG_AUDIO_NULL_DEVICE_PARAMS +#endif +}; + static const struct audio_ops_s g_audioops = { null_getcaps, /* getcaps */ @@ -161,6 +209,192 @@ static const struct audio_ops_s g_audioops = * Private Functions ****************************************************************************/ +/**************************************************************************** + * Name: null_file_init + * + * Description: Initialize the audio file for playback or capture virtual + * audio driver. + * + ****************************************************************************/ + +static int null_file_init(FAR struct audio_lowerhalf_s *dev) +{ + FAR struct null_dev_s *priv = (FAR struct null_dev_s *)dev; + char filename[64]; + int ret; + + snprintf(filename, sizeof(filename), "%s/%s_%d_%d_%d.pcm", + CONFIG_AUDIO_NULL_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: null_file_deinit + * + * Description: Deinitialize the audio file. + * + ****************************************************************************/ + +static int null_file_deinit(FAR struct audio_lowerhalf_s *dev) +{ + FAR struct null_dev_s *priv = (FAR struct null_dev_s *)dev; + + audinfo("null_file_deinit, close file\n"); + file_close(&priv->file); + + return 0; +} + +/**************************************************************************** + * Name: null_file_write + * + * Description: Write the audio data to file. + * + ****************************************************************************/ + +static int null_file_write(FAR struct audio_lowerhalf_s *dev, + FAR struct ap_buffer_s *apb) +{ + FAR struct null_dev_s *priv = (FAR struct null_dev_s *)dev; + int ret = OK; + + ret = file_write(&priv->file, (void *)apb->samp, apb->nbytes); + if (ret < 0) + { + auderr("Error write data , ret %d\n", ret); + return ret; + } + + return ret; +} + +/**************************************************************************** + * Name: null_file_read + * + * Description: Read the audio data from file. + * + ****************************************************************************/ + +static int null_file_read(FAR struct audio_lowerhalf_s *dev, + FAR struct ap_buffer_s *apb) +{ + FAR struct null_dev_s *priv = (FAR struct null_dev_s *)dev; + int ret = OK; + + ret = file_read(&priv->file, (void *)apb->samp, apb->nmaxbytes); + if (ret == 0) + { + audwarn("read file end\n"); + file_seek(&priv->file, 0, SEEK_SET); + ret = file_read(&priv->file, (void *)apb->samp, apb->nmaxbytes); + } + + if (ret < 0) + { + auderr("Error read data , ret %d\n", ret); + return ret; + } + + apb->nbytes = (apb_samp_t)ret; + apb->curbyte = 0; + apb->flags = 0; + + return ret; +} + +/**************************************************************************** + * Name: null_process_buffer + * + * Description: Process the audio buffer. + * + ****************************************************************************/ + +static int null_process_buffer(FAR struct audio_lowerhalf_s *dev, + FAR struct ap_buffer_s *apb) +{ + FAR struct null_dev_s *priv = (FAR struct null_dev_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); + + if (priv->playback) + { + gettimeofday(&tv1, NULL); + + ret = null_file_write(dev, apb); + if (ret < 0) + { + auderr("Error write 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: write time %" PRId64 ", frame time %" PRId64 ".\n", + diff_time, frame_time); + ret = OK; + goto out; + } + + sleep_time = frame_time - diff_time; + + nxsig_usleep(sleep_time); + } + else + { + ret = null_file_read(dev, apb); + if (ret < 0) + { + auderr("Error read data , ret %d\n", ret); + goto out; + } + } + + 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: null_sleep * @@ -202,6 +436,92 @@ static int null_sleep(FAR struct audio_lowerhalf_s *dev, return OK; } +/**************************************************************************** + * Name: null_capture_data_callback + * + * Description: Capture audio data from file and send to upper layer. + * + ****************************************************************************/ + +static void null_capture_data_callback(union sigval value) +{ + FAR struct null_dev_s *priv; + struct ap_buffer_s *apb; + irqstate_t flags; + int ret = 0; + + priv = (FAR struct null_dev_s *)value.sival_ptr; + + if (dq_count(&priv->pendq) <= 0 || !priv->started || priv->paused) + { + if (priv->started && !priv->paused) + { + audwarn("Lossing capture data.\n"); + } + + return; + } + + flags = enter_critical_section(); + apb = (struct ap_buffer_s *)dq_remfirst(&priv->pendq); + + ret = null_process_buffer(&priv->dev, apb); + if (ret) + { + auderr("null audio process error %d\n", ret); + priv->terminate = true; + } + + leave_critical_section(flags); + apb_free(apb); +} + +/**************************************************************************** + * Name: null_capture_delete_timer + * + * Description: Delete capture timer. + * + ****************************************************************************/ + +static int null_capture_delete_timer(FAR struct audio_lowerhalf_s *dev) +{ + FAR struct null_dev_s *priv = (FAR struct null_dev_s *)dev; + if (priv->timer_id) + timer_delete(priv->timer_id); + return OK; +} + +/**************************************************************************** + * Name: null_capture_create_timer + * + * Description: Initialize capture timer. + * + ****************************************************************************/ + +static int null_capture_create_timer(FAR struct audio_lowerhalf_s *dev) +{ + FAR struct null_dev_s *priv = (FAR struct null_dev_s *)dev; + struct itimerspec its; + struct sigevent se; + + se.sigev_notify = SIGEV_THREAD; + se.sigev_value.sival_ptr = priv; + se.sigev_notify_function = null_capture_data_callback; + se.sigev_notify_attributes = NULL; + + if (timer_create(CLOCK_MONOTONIC, &se, &priv->timer_id) < 0) + return -errno; + + its.it_value.tv_sec = 0; + its.it_value.tv_nsec = priv->dev_params->period_time * 1000 * 1000; + its.it_interval.tv_sec = 0; + its.it_interval.tv_nsec = priv->dev_params->period_time * 1000 * 1000; + + if (timer_settime(priv->timer_id, 0, &its, NULL) < 0) + return -errno; + return 0; +} + /**************************************************************************** * Name: null_getcaps * @@ -235,7 +555,11 @@ static int null_getcaps(FAR struct audio_lowerhalf_s *dev, int type, * must then call us back for specific info for each capability. */ - caps->ac_channels = 2; /* Stereo output */ + if (priv->config_params) + caps->ac_channels = (priv->dev_params->channels[0] << 4) | + (priv->dev_params->channels[1] & 0x0f); + else + caps->ac_channels = 2; switch (caps->ac_subtype) { @@ -252,6 +576,24 @@ static int null_getcaps(FAR struct audio_lowerhalf_s *dev, int type, break; + case AUDIO_FMT_PCM: + if (priv->config_params) + { + caps->ac_controls.b[0] = + null_convert_format(priv->dev_params->format[0]); + caps->ac_controls.b[1] = + null_convert_format(priv->dev_params->format[1]); + caps->ac_controls.b[2] = + null_convert_format(priv->dev_params->format[2]); + caps->ac_controls.b[3] = + null_convert_format(priv->dev_params->format[3]); + } + else + { + caps->ac_controls.b[0] = AUDIO_SUBFMT_END; + } + break; + case AUDIO_FMT_MIDI: /* We only support Format 0 */ @@ -271,21 +613,37 @@ static int null_getcaps(FAR struct audio_lowerhalf_s *dev, int type, case AUDIO_TYPE_OUTPUT: case AUDIO_TYPE_INPUT: - caps->ac_channels = 2; + if (priv->config_params) + { + caps->ac_channels = (priv->dev_params->channels[0] << 4) | + (priv->dev_params->channels[1] & 0x0f); + } + else + caps->ac_channels = 2; /* Stereo output */ 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_8K | - AUDIO_SAMP_RATE_11K | - AUDIO_SAMP_RATE_16K | - AUDIO_SAMP_RATE_22K | - AUDIO_SAMP_RATE_32K | - AUDIO_SAMP_RATE_44K | - AUDIO_SAMP_RATE_48K; + if (priv->config_params) + caps->ac_controls.hw[0] = + null_convert_samplerate(priv->dev_params->samplerate[0]) | + null_convert_samplerate(priv->dev_params->samplerate[1]) | + null_convert_samplerate(priv->dev_params->samplerate[2]) | + null_convert_samplerate(priv->dev_params->samplerate[3]); + else + caps->ac_controls.hw[0] = AUDIO_SAMP_RATE_8K | + AUDIO_SAMP_RATE_11K | + AUDIO_SAMP_RATE_16K | + AUDIO_SAMP_RATE_22K | + AUDIO_SAMP_RATE_32K | + AUDIO_SAMP_RATE_44K | + AUDIO_SAMP_RATE_48K; break; case AUDIO_FMT_MP3: @@ -395,12 +753,12 @@ static int null_configure(FAR struct audio_lowerhalf_s *dev, #endif { FAR struct null_dev_s *priv = (FAR struct null_dev_s *)dev; + int ret; audinfo("ac_type: %d\n", caps->ac_type); if (priv->mqname[0] == '\0') { struct mq_attr attr; - int ret; /* Create a message queue for the worker thread */ @@ -458,9 +816,18 @@ static int null_configure(FAR struct audio_lowerhalf_s *dev, case AUDIO_TYPE_OUTPUT: case AUDIO_TYPE_INPUT: + if (priv->config_params) + { + 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; + * caps->ac_controls.hw[0] + * caps->ac_controls.b[2] / 8; + 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]); @@ -500,9 +867,12 @@ static int null_shutdown(FAR struct audio_lowerhalf_s *dev) static void *null_workerthread(pthread_addr_t pvarg) { FAR struct null_dev_s *priv = (FAR struct null_dev_s *) pvarg; + FAR struct ap_buffer_s *apb; struct audio_msg_s msg; - int msglen; + struct mq_attr attr; unsigned int prio; + int msglen; + int ret; audinfo("Entry\n"); @@ -536,12 +906,52 @@ static void *null_workerthread(pthread_addr_t pvarg) #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: - null_sleep(&priv->dev, (FAR struct ap_buffer_s *)msg.u.ptr); + if (priv->config_params) + { + apb = (FAR struct ap_buffer_s *)msg.u.ptr; + ret = null_process_buffer(&priv->dev, apb); + if (ret) + { + auderr("null audio process error %d\n", ret); + priv->terminate = true; + } + } + else + null_sleep(&priv->dev, (FAR struct ap_buffer_s *)msg.u.ptr); break; case AUDIO_MSG_COMPLETE: @@ -557,8 +967,15 @@ static void *null_workerthread(pthread_addr_t pvarg) file_mq_close(&priv->mq); file_mq_unlink(priv->mqname); - priv->mqname[0] = '\0'; priv->terminate = false; + priv->paused = false; + priv->mqname[0] = '\0'; + + if (priv->config_params) + { + null_file_deinit(&priv->dev); + audinfo("Exit %s\n", priv->dev_params->dev_name); + } /* Send an AUDIO_MSG_COMPLETE message to the client */ @@ -592,7 +1009,14 @@ static int null_start(FAR struct audio_lowerhalf_s *dev) FAR void *value; int ret; - audinfo("Entry\n"); + priv->terminate = false; + priv->paused = false; + + ret = null_file_init(dev); + if (ret < 0) + { + return ret; + } /* Join any old worker thread we had created to prevent a memory leak */ @@ -618,10 +1042,21 @@ static int null_start(FAR struct audio_lowerhalf_s *dev) } else { - pthread_setname_np(priv->threadid, "null audio"); + pthread_setname_np(priv->threadid, "null_audio"); audinfo("Created worker thread\n"); } + if (!priv->playback) + { + ret = null_capture_create_timer(&priv->dev); + if (ret < 0) + { + auderr("Capture device create timer failed.\n"); + return ret; + } + } + + priv->started = true; audinfo("Return %d\n", ret); return ret; } @@ -656,6 +1091,14 @@ static int null_stop(FAR struct audio_lowerhalf_s *dev) file_mq_send(&priv->mq, (FAR const char *)&term_msg, sizeof(term_msg), CONFIG_AUDIO_NULL_MSG_PRIO); + if (!priv->playback) + { + null_capture_delete_timer(dev); + } + + priv->started = false; + priv->paused = false; + /* Join the worker thread */ pthread_join(priv->threadid, &value); @@ -686,7 +1129,15 @@ static int null_pause(FAR struct audio_lowerhalf_s *dev, FAR void *session) static int null_pause(FAR struct audio_lowerhalf_s *dev) #endif { - audinfo("Return OK\n"); + FAR struct null_dev_s *priv = (FAR struct null_dev_s *)dev; + + if (!priv->playback) + { + null_capture_delete_timer(dev); + } + + priv->paused = true; + audinfo("%s pause\n", priv->dev_params->dev_name); return OK; } #endif /* CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME */ @@ -705,7 +1156,14 @@ static int null_resume(FAR struct audio_lowerhalf_s *dev, FAR void *session) static int null_resume(FAR struct audio_lowerhalf_s *dev) #endif { - audinfo("Return OK\n"); + FAR struct null_dev_s *priv = (FAR struct null_dev_s *)dev; + priv->paused = false; + if (!priv->playback) + { + null_capture_create_timer(dev); + } + + audinfo("%s resume\n", priv->dev_params->dev_name); return OK; } #endif /* CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME */ @@ -722,20 +1180,32 @@ static int null_enqueuebuffer(FAR struct audio_lowerhalf_s *dev, { FAR struct null_dev_s *priv = (FAR struct null_dev_s *)dev; struct audio_msg_s msg; + irqstate_t flags; 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; + if (priv->playback) + { + msg.msg_id = AUDIO_MSG_ENQUEUE; + msg.u.ptr = apb; - ret = file_mq_send(&priv->mq, (FAR const char *)&msg, - sizeof(msg), CONFIG_AUDIO_NULL_MSG_PRIO); - if (ret < 0) + ret = file_mq_send(&priv->mq, (FAR const char *)&msg, + sizeof(msg), CONFIG_AUDIO_NULL_MSG_PRIO); + if (ret < 0) + { + auderr("ERROR: file_mq_send failed: %d\n", ret); + } + } + else { - auderr("ERROR: file_mq_send failed: %d\n", ret); + flags = enter_critical_section(); + apb_reference(apb); + apb->dq_entry.flink = NULL; + dq_addlast(&apb->dq_entry, &priv->pendq); + leave_critical_section(flags); } audinfo("Return OK\n"); @@ -768,6 +1238,7 @@ static int null_cancelbuffer(FAR struct audio_lowerhalf_s *dev, static int null_ioctl(FAR struct audio_lowerhalf_s *dev, int cmd, unsigned long arg) { + FAR struct null_dev_s *priv = (FAR struct null_dev_s *)dev; int ret = OK; #ifdef CONFIG_AUDIO_DRIVER_SPECIFIC_BUFFERS FAR struct ap_buffer_info_s *bufinfo; @@ -791,17 +1262,28 @@ static int null_ioctl(FAR struct audio_lowerhalf_s *dev, int cmd, /* Report our preferred buffer size and quantity */ -#ifdef CONFIG_AUDIO_DRIVER_SPECIFIC_BUFFERS case AUDIOIOC_GETBUFFERINFO: { audinfo("AUDIOIOC_GETBUFFERINFO:\n"); - bufinfo = (FAR struct ap_buffer_info_s *) arg; - bufinfo->buffer_size = CONFIG_AUDIO_NULL_BUFFER_SIZE; - bufinfo->nbuffers = CONFIG_AUDIO_NULL_NUM_BUFFERS; +#ifdef CONFIG_AUDIO_DRIVER_SPECIFIC_BUFFERS + bufinfo = (FAR struct ap_buffer_info_s *) arg; + if (priv->config_params) + { + bufinfo->buffer_size = (priv->scaler * + priv->dev_params->period_time) / 1000; + bufinfo->nbuffers = priv->dev_params->periods; + } + else + { + bufinfo->buffer_size = CONFIG_AUDIO_NULL_BUFFER_SIZE; + bufinfo->nbuffers = CONFIG_AUDIO_NULL_NUM_BUFFERS; + } +#else + audwarn("AUDIOIOC_GETBUFFERINFO Return EPERM\n"); + return -EPERM; +#endif } break; -#endif - default: ret = -ENOTTY; break; @@ -825,7 +1307,9 @@ static int null_reserve(FAR struct audio_lowerhalf_s *dev, static int null_reserve(FAR struct audio_lowerhalf_s *dev) #endif { - audinfo("Return OK\n"); + FAR struct null_dev_s *priv = (FAR struct null_dev_s *)dev; + priv->terminate = false; + audinfo("%s reserve\n", priv->dev_params->dev_name); return OK; } @@ -857,6 +1341,146 @@ static int null_release(FAR struct audio_lowerhalf_s *dev) return OK; } +/**************************************************************************** + * Name: null_convert_samplerate + * + * Description: Convert the samplerate to Nuttx audio samplerate. + * + ****************************************************************************/ + +static uint32_t null_convert_samplerate(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: null_convert_format + * + * Description: Convert the format to Nuttx audio format. + * + ****************************************************************************/ + +static uint8_t null_convert_format(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: null_init_device + * + * Description: Initialize the audio device. + * + ****************************************************************************/ + +static FAR struct audio_lowerhalf_s * +null_init_device(bool playback, FAR struct null_dev_params_s *dev_params) +{ + FAR struct null_dev_s *priv; + + /* Allocate the null audio device structure */ + + priv = (FAR struct null_dev_s *)kmm_zalloc(sizeof(struct null_dev_s)); + if (!priv) + { + auderr("ERROR: Failed to allocate null audio device\n"); + return NULL; + } + + priv->dev.ops = &g_audioops; + priv->dev_params = dev_params; + priv->playback = playback; + priv->started = false; + priv->paused = false; + priv->terminate = false; + priv->config_params = true; + + if (!playback) + { + dq_init(&priv->pendq); + } + + return &priv->dev; +} + +/**************************************************************************** + * Name: null_audio_register_with_params + * + * Description: Register the null audio device with the audio device. + * + ****************************************************************************/ + +static int null_audio_register_with_params(void) +{ + int ret; + int i; + + for (i = 0; i < ARRAY_ELEMS(g_dev_params); i++) + { + if (!strncmp(g_dev_params[i].mode, "playback", strlen("playback"))) + { + ret = audio_register(g_dev_params[i].dev_name, + null_init_device(true, &g_dev_params[i])); + } + else + { + ret = audio_register(g_dev_params[i].dev_name, + null_init_device(false, &g_dev_params[i])); + } + } + + return ret; +} + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -900,3 +1524,28 @@ FAR struct audio_lowerhalf_s *audio_null_initialize(bool playback) auderr("ERROR: Failed to allocate null audio device\n"); return NULL; } + +/**************************************************************************** + * Name: null_audio_register + * + * Description: + * Initialize and register null audio device. + * + * Returned Value: + * 0 is returned on success; + * others is returned on failure. + * + ****************************************************************************/ + +int null_audio_register(void) +{ + int err; + + err = null_audio_register_with_params(); + if (err < 0) + { + auderr("ERROR: Failed to register null audio device\n"); + } + + return err; +} diff --git a/include/nuttx/audio/audio_null.h b/include/nuttx/audio/audio_null.h index 80df3b2f52f28..3f6fbc7549719 100644 --- a/include/nuttx/audio/audio_null.h +++ b/include/nuttx/audio/audio_null.h @@ -113,6 +113,20 @@ struct audio_lowerhalf_s; /* Forward reference. Defined in nuttx/audio/audio.h * FAR struct audio_lowerhalf_s *audio_null_initialize(bool playback); +/**************************************************************************** + * Name: null_audio_register + * + * Description: + * Initialize and register fake audio device. + * + * Returned Value: + * 0 is returned on success; + * others is returned on failure. + * + ****************************************************************************/ + +int null_audio_register(void); + #undef EXTERN #ifdef __cplusplus } diff --git a/tools/cfgdefine.c b/tools/cfgdefine.c index 575aeb9f85535..26fa3ebb9d89f 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_AUDIO_NULL_DEVICE_PARAMS", /* Arguments for the null audio device */ /* NxWidgets/NxWM */