/* 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 #include #include #include #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; } }