From 4f7abe0231394cfb8207c22b62546237ca81893d Mon Sep 17 00:00:00 2001 From: jinxiuxu Date: Wed, 9 Aug 2023 10:21:30 +0800 Subject: [PATCH] apps/testing/drivertest:add audio drivertest Signed-off-by: jinxiuxu --- testing/drivertest/Makefile | 5 + testing/drivertest/drivertest_audio.c | 794 ++++++++++++++++++++++++++ 2 files changed, 799 insertions(+) create mode 100644 testing/drivertest/drivertest_audio.c diff --git a/testing/drivertest/Makefile b/testing/drivertest/Makefile index 5005f5152..bc4a5ecf4 100644 --- a/testing/drivertest/Makefile +++ b/testing/drivertest/Makefile @@ -94,4 +94,9 @@ MAINSRC += drivertest_uart.c PROGNAME += cmocka_driver_uart endif +ifneq ($(CONFIG_AUDIO),) +MAINSRC += drivertest_audio.c +PROGNAME += cmocka_driver_audio +endif + include $(APPDIR)/Application.mk diff --git a/testing/drivertest/drivertest_audio.c b/testing/drivertest/drivertest_audio.c new file mode 100644 index 000000000..6040bf17e --- /dev/null +++ b/testing/drivertest/drivertest_audio.c @@ -0,0 +1,794 @@ +/**************************************************************************** + * apps/testing/drivertest/drivertest_audio.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 + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#ifndef MAX +# define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif + +#define OPTARG_TO_VALUE(value, type, base) \ + do \ + { \ + FAR char *ptr; \ + value = (type)strtoul(optarg, &ptr, base); \ + if (*ptr != '\0') \ + { \ + printf("Parameter error: %s\n", optarg); \ + audio_test_help(argv[0], EXIT_FAILURE); \ + } \ + } while (0) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct audio_state_s +{ + char outfile[PATH_MAX]; + char outdev[PATH_MAX]; + char indev[PATH_MAX]; + int outdev_fd; + int indev_fd; + int out_fd; + + uint32_t samprate; + uint32_t format; + uint32_t bpsamp; + uint32_t chans; + uint32_t chmap; + + char mqname[16]; + int direction; + uint32_t duration; + uint32_t idx; + mqd_t mq; + +#ifdef CONFIG_AUDIO_MULTI_SESSION + FAR void *session; +#endif +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: audio_test_writebuffer + ****************************************************************************/ + +static int audio_test_writebuffer(FAR struct audio_state_s *state, + FAR struct ap_buffer_s *apb) +{ + int ret = 0; + + if (state->out_fd == -1) + { + return -ENODATA; + } + + ret = write(state->out_fd, apb->samp, apb->nbytes); + if (ret < 0) + { + return ret; + } + + apb->curbyte = 0; + apb->flags = 0; + + return OK; +} + +/**************************************************************************** + * Name: player_readbuffer + ****************************************************************************/ + +static int audio_test_readbuffer(FAR struct audio_state_s *state, + FAR struct ap_buffer_s *apb) +{ + if (state->out_fd == -1) + { + return -ENODATA; + } + + apb->nbytes = read(state->out_fd, apb->samp, apb->nmaxbytes); + apb->curbyte = 0; + apb->flags = 0; + + if (apb->nbytes < apb->nmaxbytes) + { + close(state->out_fd); + state->out_fd = -1; + apb->flags |= AUDIO_APB_FINAL; + } + + return OK; +} + +/**************************************************************************** + * Name: audio_test_enqueuebuffer + ****************************************************************************/ + +static int audio_test_enqueuebuffer(FAR struct audio_state_s *state, + FAR struct ap_buffer_s *apb, + int direction) +{ + struct audio_buf_desc_s bufdesc; + int ret = 0; + int fd; + + fd = direction == AUDIO_TYPE_OUTPUT ? + state->outdev_fd : + state->indev_fd; + + apb->nbytes = apb->nmaxbytes; + +#ifdef CONFIG_AUDIO_MULTI_SESSION + bufdesc.session = state->session; +#endif + + bufdesc.numbytes = apb->nbytes; + bufdesc.u.buffer = apb; + + ret = ioctl(fd, AUDIOIOC_ENQUEUEBUFFER, (unsigned long)&bufdesc); + if (ret < 0) + { + return -errno; + } + + return OK; +} + +static int audio_test_alloc_buffer(FAR struct audio_state_s *state, + FAR struct ap_buffer_s **bufs, + FAR struct ap_buffer_info_s *buf_info, + int direction) +{ + struct audio_buf_desc_s buf_desc; + int ret = 0; + int fd; + int x; + + fd = direction == AUDIO_TYPE_OUTPUT ? + state->outdev_fd : + state->indev_fd; + + for (x = 0; x < buf_info->nbuffers; x++) + { +#ifdef CONFIG_AUDIO_MULTI_SESSION + buf_desc.session = state->session; +#endif + buf_desc.numbytes = buf_info->buffer_size; + buf_desc.u.pbuffer = &bufs[x]; + + ret = ioctl(fd, AUDIOIOC_ALLOCBUFFER, (unsigned long)&buf_desc); + if (ret != sizeof(buf_desc)) + { + goto err_out; + } + } + + return OK; + +err_out: + free(bufs); + return ret; +} + +static int audio_test_free_buffer(FAR struct audio_state_s *state, + FAR struct ap_buffer_s **bufs, + FAR struct ap_buffer_info_s *buf_info, + int direction) +{ + struct audio_buf_desc_s buf_desc; + int ret = 0; + int fd; + int x; + + fd = direction == AUDIO_TYPE_OUTPUT ? + state->outdev_fd : + state->indev_fd; + + for (x = 0; x < buf_info->nbuffers; x++) + { + if (bufs[x] != NULL) + { +#ifdef CONFIG_AUDIO_MULTI_SESSION + buf_desc.session = state->session; +#endif + buf_desc.u.buffer = bufs[x]; + ret = ioctl(fd, AUDIOIOC_FREEBUFFER, (unsigned long)&buf_desc); + if (ret < 0) + { + break; + } + } + } + + return ret; +} + +static void audio_test_help(FAR const char *progname, int exitcode) +{ + printf("Usage: %s\n" + " -a \n" + " -t \n" + " -i \n" + " -o \n" + " -p \n" + " -f \n" + " -b \n" + " -s \n" + " -c \n", + progname); + printf(" [-a testcase] selects the testcase\n" + " Case 1: Capture\n" + " Case 2: Playack\n" + " Case 3: First Capture, and then Playback\n" + ); + printf(" -h shows this message and exits\n"); + + exit(exitcode); +} + +/**************************************************************************** + * Name: parse_commandline + ****************************************************************************/ + +static void parse_commandline(FAR struct audio_state_s *state, int argc, + FAR char **argv) +{ + int option; + + while ((option = getopt(argc, argv, "a:t::o::i::f::p:c::b::s::")) != ERROR) + { + switch (option) + { + case 'a': + { + OPTARG_TO_VALUE(state->direction, uint32_t, 10); + break; + } + + case 't': + { + OPTARG_TO_VALUE(state->duration, uint32_t, 10); + break; + } + + case 'o': + { + strcpy(state->outdev, optarg); + break; + } + + case 'i': + { + strcpy(state->indev, optarg); + break; + } + + case 'f': + { + if (!strcmp(optarg, "mp3")) + { + state->format = AUDIO_FMT_MP3; + } + else + { + state->format = AUDIO_FMT_PCM; + } + break; + } + + case 'p': + { + strcpy(state->outfile, optarg); + break; + } + + case 'c': + { + OPTARG_TO_VALUE(state->chans, uint32_t, 10); + break; + } + + case 'b': + { + OPTARG_TO_VALUE(state->bpsamp, uint32_t, 10); + break; + } + + case 's': + { + OPTARG_TO_VALUE(state->samprate, uint32_t, 10); + break; + } + + case '?': + printf("Unknown option: %c\n", optopt); + audio_test_help(argv[0], EXIT_FAILURE); + break; + } + } +} + +/**************************************************************************** + * Name: audio_test_prepare + ****************************************************************************/ + +static int audio_test_prepare(FAR struct audio_state_s *state, + FAR struct ap_buffer_info_s *buf_info, + int direction) +{ + struct audio_caps_desc_s cap_desc; + struct audio_caps_s caps; + int ret = 0; + int fd; + + fd = direction == AUDIO_TYPE_OUTPUT ? + state->outdev_fd : + state->indev_fd; + + if (direction == AUDIO_TYPE_OUTPUT) + { + state->out_fd = open(state->outfile, O_RDONLY); + } + else + { + state->out_fd = open(state->outfile, O_WRONLY | O_CREAT | O_TRUNC); + } + + if (state->out_fd == -1) + { + return -ENOENT; + } + + caps.ac_len = sizeof(caps); + caps.ac_type = AUDIO_TYPE_QUERY; + caps.ac_subtype = AUDIO_TYPE_QUERY; + + ret = ioctl(fd, AUDIOIOC_GETCAPS, (unsigned long)&caps); + if (ret != caps.ac_len) + { + return ret; + } + + if ((caps.ac_controls.b[0] & direction) == 0 || + (caps.ac_format.hw & (1 << (state->format - 1))) == 0) + { + return -EINVAL; + } + +#ifdef CONFIG_AUDIO_MULTI_SESSION + ret = ioctl(fd, AUDIOIOC_RESERVE, (unsigned long)&state->session); +#else + ret = ioctl(fd, AUDIOIOC_RESERVE, 0); +#endif + +#ifdef CONFIG_AUDIO_MULTI_SESSION + cap_desc.session = state->session: +#endif + cap_desc.caps.ac_len = sizeof(struct audio_caps_s); + cap_desc.caps.ac_type = direction; + cap_desc.caps.ac_channels = state->chans; + cap_desc.caps.ac_chmap = state->chmap; + cap_desc.caps.ac_controls.hw[0] = state->samprate; + cap_desc.caps.ac_controls.b[3] = state->samprate >> 16; + cap_desc.caps.ac_controls.b[2] = state->bpsamp; + cap_desc.caps.ac_subtype = state->format; + ret = ioctl(fd, AUDIOIOC_CONFIGURE, (unsigned long)&cap_desc); + if (ret < 0) + { + return -errno; + } + + ret = ioctl(fd, AUDIOIOC_REGISTERMQ, (unsigned long)state->mq); + if (ret < 0) + { + return -errno; + } + + ret = ioctl(fd, AUDIOIOC_GETBUFFERINFO, (unsigned long)buf_info); + if (ret < 0) + { + buf_info->buffer_size = CONFIG_AUDIO_BUFFER_NUMBYTES; + buf_info->nbuffers = CONFIG_AUDIO_NUM_BUFFERS; + } + + return OK; +} + +static int audio_test_start(FAR struct audio_state_s *state, int direction) +{ + int ret = 0; + int fd; + + fd = direction == AUDIO_TYPE_OUTPUT ? + state->outdev_fd : + state->indev_fd; + +#ifdef CONFIG_AUDIO_MULTI_SESSION + ret = ioctl(fd, AUDIOIOC_START, + (unsigned long)state->session); +#else + ret = ioctl(fd, AUDIOIOC_START, 0); +#endif + + return ret; +} + +static bool audio_test_timeout(FAR struct audio_state_s *state, + int direction, struct timeval start) +{ + struct timeval now; + struct timeval delta; + struct timeval wait; + + if (direction == AUDIO_TYPE_OUTPUT) + { + return false; + } + + wait.tv_sec = state->duration; + wait.tv_usec = 0; + + gettimeofday(&now, NULL); + timersub(&now, &start, &delta); + return timercmp(&delta, &wait, >); +} + +static int audio_test_stop(FAR struct audio_state_s *state, int direction) +{ + int ret = 0; + int fd; + + fd = direction == AUDIO_TYPE_OUTPUT ? + state->outdev_fd : + state->indev_fd; + +#ifdef CONFIG_AUDIO_MULTI_SESSION + ret = ioctl(fd, AUDIOIOC_STOP, + (unsigned long)state->session); +#else + ret = ioctl(fd, AUDIOIOC_STOP, 0); +#endif + + return ret; +} + +static int audio_test_cleanup(FAR struct audio_state_s *state, int direction) +{ + int fd; + + fd = direction == AUDIO_TYPE_OUTPUT ? + state->outdev_fd : + state->indev_fd; + + ioctl(fd, AUDIOIOC_UNREGISTERMQ, (unsigned long)state->mq); + + if (state->out_fd >= 0) + { + close(state->out_fd); + state->out_fd = -1; + } + + return 0; +} + +static void audio_test_case(void **audio_state) +{ + FAR struct audio_state_s *state; + FAR struct ap_buffer_s **bufs = NULL; + struct ap_buffer_info_s buf_info; + struct audio_msg_s msg; + struct timeval start; + int directions[3] = + { + AUDIO_TYPE_INPUT, + AUDIO_TYPE_OUTPUT, + -1 + }; + + struct mq_attr attr; + unsigned int prio; + bool streaming; + bool running; + int ret = 0; + int direct; + int x = 0; + int i; + + state = (struct audio_state_s *)*audio_state; + + while (directions[x] != -1) + { + /* first round, test capture. + * second round, test playback. + * */ + + streaming = running = true; + if (!(directions[x] & state->direction)) + { + x++; + continue; + } + + direct = directions[x]; + + ret = audio_test_prepare(state, &buf_info, direct); + assert_false(ret < 0); + + bufs = (FAR struct ap_buffer_s **) + calloc(buf_info.nbuffers, sizeof(FAR void *)); + assert_true(bufs != NULL); + + ret = audio_test_alloc_buffer(state, bufs, &buf_info, direct); + assert_false(ret < 0); + + for (i = 0; i < buf_info.nbuffers; i++) + { + if (direct == AUDIO_TYPE_OUTPUT) + ret = audio_test_readbuffer(state, bufs[i]); + + if (ret < 0) + { + streaming = false; + } + else + { + ret = audio_test_enqueuebuffer(state, bufs[i], direct); + assert_false(ret); + } + } + + if (running) + { + ret = audio_test_start(state, direct); + assert_false(ret < 0); + gettimeofday(&start, NULL); + } + + printf("Start %s. \n", direct == AUDIO_TYPE_OUTPUT ? + "Playback" : "Capture"); + + while (running) + { + ret = mq_receive(state->mq, (FAR char *)&msg, sizeof(msg), &prio); + if (ret != sizeof(msg)) + { + continue; + } + + switch (msg.msg_id) + { + case AUDIO_MSG_DEQUEUE: + if (streaming) + { + if (direct == AUDIO_TYPE_INPUT) + { + ret = audio_test_writebuffer(state, msg.u.ptr); + } + else + { + ret = audio_test_readbuffer(state, msg.u.ptr); + } + + if (ret != OK) + { + streaming = false; + } + else + { + ret = audio_test_enqueuebuffer(state, + msg.u.ptr, direct); + if (ret != OK) + { + close(state->out_fd); + state->out_fd = -1; + streaming = false; + } + } + } + break; + + case AUDIO_MSG_COMPLETE: + running = false; + break; + + default: + break; + } + + /* Capture stopped */ + + if (audio_test_timeout(state, direct, start) || !streaming) + { + ret = audio_test_stop(state, direct); + assert_false(ret < 0); + running = false; + } + } + + do + { + ret = mq_getattr(state->mq, &attr); + assert_false(ret < 0); + + if (attr.mq_curmsgs == 0) + { + break; + } + + mq_receive(state->mq, (FAR char *)&msg, sizeof(msg), &prio); + } + while (ret >= 0); + + ret = audio_test_free_buffer(state, bufs, &buf_info, direct); + assert_false(ret < 0); + + ret = audio_test_cleanup(state, direct); + assert_false(ret < 0); + + memset(&buf_info, 0, sizeof(buf_info)); + free(bufs); + bufs = NULL; + + x++; + } + + return; +} + +static int audio_test_setup(FAR void **audio_state) +{ + FAR struct audio_state_s *state; + struct ap_buffer_info_s buf_info; + struct mq_attr attr; + int maxmsg = 0; + int ret = 0; + + state = (struct audio_state_s *)*audio_state; + + if (state->direction & AUDIO_TYPE_OUTPUT) + { + state->outdev_fd = open(state->outdev, O_RDWR | O_CLOEXEC); + assert_false(state->outdev_fd < 0); + + ret = ioctl(state->outdev_fd, AUDIOIOC_GETBUFFERINFO, + (unsigned long)&buf_info); + maxmsg = MAX(maxmsg, + ret >= 0 ? buf_info.nbuffers : CONFIG_AUDIO_NUM_BUFFERS); + } + + if (state->direction & AUDIO_TYPE_INPUT) + { + state->indev_fd = open(state->indev, O_RDWR | O_CLOEXEC); + assert_false(state->indev_fd < 0); + + ret = ioctl(state->indev_fd, AUDIOIOC_GETBUFFERINFO, + (unsigned long)&buf_info); + maxmsg = MAX(maxmsg, + ret >= 0 ? buf_info.nbuffers : CONFIG_AUDIO_NUM_BUFFERS); + } + + attr.mq_maxmsg = maxmsg + 8; + attr.mq_msgsize = sizeof(struct audio_msg_s); + attr.mq_curmsgs = 0; + attr.mq_flags = 0; + + snprintf(state->mqname, sizeof(state->mqname), "/tmp/%0lx", + (unsigned long)((uintptr_t)state)); + + state->mq = mq_open(state->mqname, O_RDWR | O_CREAT, 0644, &attr); + assert_false(state->mq < 0); + + return OK; +} + +static int audio_test_teardown(FAR void **audio_state) +{ + FAR struct audio_state_s *state; + state = (struct audio_state_s *)*audio_state; + + if (state->outdev_fd >= 0) + { + close(state->outdev_fd); + state->outdev_fd = -1; + } + + if (state->outdev_fd >= 0) + { + close(state->indev_fd); + state->indev_fd = -1; + } + + mq_close(state->mq); + mq_unlink(state->mqname); + + return 0; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: loopback_test_main + ****************************************************************************/ + +int main(int argc, FAR char *argv[]) +{ + struct audio_state_s state = { + .outdev = "/dev/audio/pcm0p", + .indev = "/dev/audio/pcm0c", + .duration = 10, + .chans = 2, + .bpsamp = 16, + .samprate = 44100, + .format = AUDIO_FMT_PCM, + .chmap = 0, + .outdev_fd = -1, + .indev_fd = -1, + .out_fd = -1, + }; + + parse_commandline(&state, argc, argv); + + const struct CMUnitTest tests[] = + { + cmocka_unit_test_prestate_setup_teardown(audio_test_case, + audio_test_setup, + audio_test_teardown, + &state), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +}