/**************************************************************************** * 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 #include /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #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); }