blob: cb7d5f3a1a90e68679a3fd0796da432c013f20f2 [file] [log] [blame]
/* Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include <sys/ioctl.h>
#include <pthread.h>
#include <sys/param.h>
#include <syslog.h>
#include "audio_thread.h"
#include "byte_buffer.h"
#include "cras_audio_area.h"
#include "cras_config.h"
#include "cras_iodev.h"
#include "cras_iodev_list.h"
#include "cras_types.h"
#include "cras_util.h"
#include "test_iodev.h"
#include "utlist.h"
#define TEST_BUFFER_SIZE (16 * 1024)
static size_t test_supported_rates[] = { 16000, 0 };
static size_t test_supported_channel_counts[] = { 1, 0 };
static snd_pcm_format_t test_supported_formats[] = { SND_PCM_FORMAT_S16_LE, 0 };
struct test_iodev {
struct cras_iodev base;
int fd;
struct byte_buffer *audbuff;
unsigned int fmt_bytes;
};
/*
* iodev callbacks.
*/
static int frames_queued(const struct cras_iodev *iodev,
struct timespec *tstamp)
{
struct test_iodev *testio = (struct test_iodev *)iodev;
int available;
if (testio->fd < 0)
return 0;
ioctl(testio->fd, FIONREAD, &available);
clock_gettime(CLOCK_MONOTONIC_RAW, tstamp);
return available / testio->fmt_bytes;
}
static int delay_frames(const struct cras_iodev *iodev)
{
return 0;
}
static int close_dev(struct cras_iodev *iodev)
{
struct test_iodev *testio = (struct test_iodev *)iodev;
byte_buffer_destroy(&testio->audbuff);
testio->audbuff = NULL;
cras_iodev_free_audio_area(iodev);
return 0;
}
static int configure_dev(struct cras_iodev *iodev)
{
struct test_iodev *testio = (struct test_iodev *)iodev;
if (iodev->format == NULL)
return -EINVAL;
cras_iodev_init_audio_area(iodev, iodev->format->num_channels);
testio->fmt_bytes = cras_get_format_bytes(iodev->format);
testio->audbuff =
byte_buffer_create(TEST_BUFFER_SIZE * testio->fmt_bytes);
return 0;
}
static int get_buffer(struct cras_iodev *iodev, struct cras_audio_area **area,
unsigned *frames)
{
struct test_iodev *testio = (struct test_iodev *)iodev;
unsigned int readable;
uint8_t *buff;
buff = buf_read_pointer_size(testio->audbuff, &readable);
*frames = MIN(*frames, readable);
iodev->area->frames = *frames;
cras_audio_area_config_buf_pointers(iodev->area, iodev->format, buff);
*area = iodev->area;
return 0;
}
static int put_buffer(struct cras_iodev *iodev, unsigned frames)
{
struct test_iodev *testio = (struct test_iodev *)iodev;
/* Input */
buf_increment_read(testio->audbuff,
(size_t)frames * (size_t)testio->fmt_bytes);
return 0;
}
static int get_buffer_fd_read(struct cras_iodev *iodev,
struct cras_audio_area **area, unsigned *frames)
{
struct test_iodev *testio = (struct test_iodev *)iodev;
int nread;
uint8_t *write_ptr;
unsigned int avail;
if (testio->fd < 0) {
*frames = 0;
return 0;
}
write_ptr = buf_write_pointer_size(testio->audbuff, &avail);
avail = MIN(avail, *frames * testio->fmt_bytes);
nread = read(testio->fd, write_ptr, avail);
if (nread <= 0) {
*frames = 0;
audio_thread_rm_callback(testio->fd);
close(testio->fd);
testio->fd = -1;
return 0;
}
buf_increment_write(testio->audbuff, nread);
*frames = nread / testio->fmt_bytes;
iodev->area->frames = *frames;
cras_audio_area_config_buf_pointers(iodev->area, iodev->format,
write_ptr);
*area = iodev->area;
return nread;
}
static void update_active_node(struct cras_iodev *iodev, unsigned node_idx,
unsigned dev_enabled)
{
}
static void play_file_as_hotword(struct test_iodev *testio, const char *path)
{
if (testio->fd >= 0) {
/* Remove audio thread callback from main thread. */
audio_thread_rm_callback_sync(
cras_iodev_list_get_audio_thread(), testio->fd);
close(testio->fd);
}
testio->fd = open(path, O_RDONLY);
buf_reset(testio->audbuff);
}
/*
* Exported Interface.
*/
struct cras_iodev *test_iodev_create(enum CRAS_STREAM_DIRECTION direction,
enum TEST_IODEV_TYPE type)
{
struct test_iodev *testio;
struct cras_iodev *iodev;
struct cras_ionode *node;
if (direction != CRAS_STREAM_INPUT || type != TEST_IODEV_HOTWORD)
return NULL;
testio = calloc(1, sizeof(*testio));
if (testio == NULL)
return NULL;
iodev = &testio->base;
iodev->direction = direction;
testio->fd = -1;
iodev->supported_rates = test_supported_rates;
iodev->supported_channel_counts = test_supported_channel_counts;
iodev->supported_formats = test_supported_formats;
iodev->buffer_size = TEST_BUFFER_SIZE;
iodev->configure_dev = configure_dev;
iodev->close_dev = close_dev;
iodev->frames_queued = frames_queued;
iodev->delay_frames = delay_frames;
if (type == TEST_IODEV_HOTWORD)
iodev->get_buffer = get_buffer_fd_read;
else
iodev->get_buffer = get_buffer;
iodev->put_buffer = put_buffer;
iodev->update_active_node = update_active_node;
/*
* Record max supported channels into cras_iodev_info.
* The value is the max of test_supported_channel_counts.
*/
iodev->info.max_supported_channels = 1;
/* Create an empty ionode */
node = (struct cras_ionode *)calloc(1, sizeof(*node));
node->dev = iodev;
node->plugged = 1;
if (type == TEST_IODEV_HOTWORD)
node->type = CRAS_NODE_TYPE_HOTWORD;
else
node->type = CRAS_NODE_TYPE_UNKNOWN;
node->volume = 100;
node->software_volume_needed = 0;
node->ui_gain_scaler = 1.0f;
strcpy(node->name, "(default)");
cras_iodev_add_node(iodev, node);
cras_iodev_set_active_node(iodev, node);
/* Finally add it to the appropriate iodev list. */
snprintf(iodev->info.name, ARRAY_SIZE(iodev->info.name), "Tester");
iodev->info.name[ARRAY_SIZE(iodev->info.name) - 1] = '\0';
cras_iodev_list_add_input(iodev);
return iodev;
}
void test_iodev_destroy(struct cras_iodev *iodev)
{
struct test_iodev *testio = (struct test_iodev *)iodev;
cras_iodev_list_rm_input(iodev);
free(iodev->active_node);
cras_iodev_free_resources(iodev);
free(testio);
}
unsigned int test_iodev_add_samples(struct test_iodev *testio, uint8_t *samples,
unsigned int count)
{
unsigned int avail;
uint8_t *write_ptr;
write_ptr = buf_write_pointer_size(testio->audbuff, &avail);
count = MIN(count, avail);
memcpy(write_ptr, samples, (size_t)count * (size_t)testio->fmt_bytes);
buf_increment_write(testio->audbuff,
(size_t)count * (size_t)testio->fmt_bytes);
return count;
}
void test_iodev_command(struct cras_iodev *iodev,
enum CRAS_TEST_IODEV_CMD command, unsigned int data_len,
const uint8_t *data)
{
struct test_iodev *testio = (struct test_iodev *)iodev;
if (!cras_iodev_is_open(iodev))
return;
switch (command) {
case TEST_IODEV_CMD_HOTWORD_TRIGGER:
play_file_as_hotword(testio, (char *)data);
break;
default:
break;
}
}