nuttx-apps/testing/drivertest/drivertest_audio.c

792 lines
20 KiB
C
Raw Normal View History

/****************************************************************************
* 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 <nuttx/config.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/params.h>
#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <assert.h>
#include <cmocka.h>
#include <errno.h>
#include <dirent.h>
#include <mqueue.h>
#include <debug.h>
#include <unistd.h>
#include <nuttx/audio/audio.h>
/****************************************************************************
* 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 <test case>\n"
" -t <duration>\n"
" -i <input device e.g./dev/audio/pcm0c>\n"
" -o <output device e.g./dev/audio/pcm0p>\n"
" -p <output file>\n"
" -f <format>\n"
" -b <bytes per sample> \n"
" -s <sample rate> \n"
" -c <channles> \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);
}