blob: 3471c756a1de93d964682f7773bd547cf1532de5 [file] [log] [blame]
/* Copyright (c) 2012 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 <pthread.h>
#include <sys/param.h>
#include <syslog.h>
#include "cras_audio_area.h"
#include "cras_config.h"
#include "cras_iodev.h"
#include "cras_iodev_list.h"
#include "cras_rstream.h"
#include "cras_types.h"
#include "utlist.h"
#define EMPTY_BUFFER_SIZE (32 * 1024)
#define MAX_EMPTY_FRAME_SIZE 8
#define EMPTY_FRAMES (EMPTY_BUFFER_SIZE / MAX_EMPTY_FRAME_SIZE)
static size_t empty_supported_rates[] = { 44100, 48000, 0 };
static size_t empty_supported_channel_counts[] = { 1, 2, 0 };
static snd_pcm_format_t empty_supported_formats[] = {
SND_PCM_FORMAT_S16_LE, SND_PCM_FORMAT_S24_LE, SND_PCM_FORMAT_S32_LE,
SND_PCM_FORMAT_S24_3LE, 0
};
struct empty_iodev {
struct cras_iodev base;
uint8_t *audio_buffer;
uint64_t read_frames, written_frames;
struct timespec dev_start_time;
};
/*
* Current level of the audio buffer. This is made up based on what has been
* read/written and how long it has been since the start. Simulates audio
* hardware running at the given sample rate.
*/
static unsigned int current_level(const struct cras_iodev *iodev)
{
struct empty_iodev *empty_iodev = (struct empty_iodev *)iodev;
uint64_t frames_since_start, nframes;
if (iodev->active_node->type == CRAS_NODE_TYPE_HOTWORD)
return 0;
frames_since_start = cras_frames_since_time(
&empty_iodev->dev_start_time, iodev->format->frame_rate);
if (iodev->direction == CRAS_STREAM_INPUT) {
nframes = frames_since_start - empty_iodev->read_frames;
return MIN(nframes, EMPTY_FRAMES);
}
/* output */
if (empty_iodev->written_frames <= frames_since_start)
return 0;
return empty_iodev->written_frames - frames_since_start;
}
/*
* iodev callbacks.
*/
static int frames_queued(const struct cras_iodev *iodev,
struct timespec *tstamp)
{
clock_gettime(CLOCK_MONOTONIC_RAW, tstamp);
return current_level(iodev);
}
static int delay_frames(const struct cras_iodev *iodev)
{
return 0;
}
static int close_dev(struct cras_iodev *iodev)
{
struct empty_iodev *empty_iodev = (struct empty_iodev *)iodev;
free(empty_iodev->audio_buffer);
empty_iodev->audio_buffer = NULL;
cras_iodev_free_audio_area(iodev);
return 0;
}
static int configure_dev(struct cras_iodev *iodev)
{
struct empty_iodev *empty_iodev = (struct empty_iodev *)iodev;
if (iodev->format == NULL)
return -EINVAL;
cras_iodev_init_audio_area(iodev, iodev->format->num_channels);
empty_iodev->audio_buffer = calloc(1, EMPTY_BUFFER_SIZE);
empty_iodev->read_frames = 0;
empty_iodev->written_frames = 0;
clock_gettime(CLOCK_MONOTONIC_RAW, &empty_iodev->dev_start_time);
return 0;
}
static int get_buffer(struct cras_iodev *iodev, struct cras_audio_area **area,
unsigned *frames)
{
struct empty_iodev *empty_iodev = (struct empty_iodev *)iodev;
unsigned int avail, current;
if (iodev->direction == CRAS_STREAM_OUTPUT) {
avail = EMPTY_FRAMES - current_level(iodev);
*frames = MIN(*frames, avail);
} else {
current = current_level(iodev);
*frames = MIN(*frames, current);
}
iodev->area->frames = *frames;
cras_audio_area_config_buf_pointers(iodev->area, iodev->format,
empty_iodev->audio_buffer);
*area = iodev->area;
return 0;
}
/*
* Returns -EPIPE if there are not enough frames or spaces in device buffer.
* It matches other alsa-based devices.
*/
static int put_buffer(struct cras_iodev *iodev, unsigned frames)
{
struct empty_iodev *empty_iodev = (struct empty_iodev *)iodev;
if (iodev->direction == CRAS_STREAM_INPUT) {
if (current_level(iodev) < frames)
return -EPIPE;
empty_iodev->read_frames += frames;
} else {
if (EMPTY_FRAMES - current_level(iodev) < frames)
return -EPIPE;
empty_iodev->written_frames += frames;
}
return 0;
}
static int flush_buffer(struct cras_iodev *iodev)
{
struct empty_iodev *empty_iodev = (struct empty_iodev *)iodev;
if (iodev->direction == CRAS_STREAM_INPUT)
empty_iodev->read_frames = 0;
else
empty_iodev->written_frames = 0;
clock_gettime(CLOCK_MONOTONIC_RAW, &empty_iodev->dev_start_time);
return 0;
}
static void update_active_node(struct cras_iodev *iodev, unsigned node_idx,
unsigned dev_enabled)
{
}
/*
* Exported Interface.
*/
struct cras_iodev *empty_iodev_create(enum CRAS_STREAM_DIRECTION direction,
enum CRAS_NODE_TYPE node_type)
{
struct empty_iodev *empty_iodev;
struct cras_iodev *iodev;
struct cras_ionode *node;
if (direction != CRAS_STREAM_INPUT && direction != CRAS_STREAM_OUTPUT)
return NULL;
empty_iodev = calloc(1, sizeof(*empty_iodev));
if (empty_iodev == NULL)
return NULL;
iodev = &empty_iodev->base;
iodev->direction = direction;
iodev->supported_rates = empty_supported_rates;
iodev->supported_channel_counts = empty_supported_channel_counts;
iodev->supported_formats = empty_supported_formats;
iodev->buffer_size = EMPTY_FRAMES;
iodev->configure_dev = configure_dev;
iodev->close_dev = close_dev;
iodev->frames_queued = frames_queued;
iodev->delay_frames = delay_frames;
iodev->get_buffer = get_buffer;
iodev->put_buffer = put_buffer;
iodev->flush_buffer = flush_buffer;
iodev->update_active_node = update_active_node;
iodev->no_stream = cras_iodev_default_no_stream_playback;
/* Create an empty ionode */
node = (struct cras_ionode *)calloc(1, sizeof(*node));
node->dev = iodev;
node->type = node_type;
node->volume = 100;
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. */
if (direction == CRAS_STREAM_INPUT) {
if (node->type == CRAS_NODE_TYPE_HOTWORD) {
snprintf(iodev->info.name, ARRAY_SIZE(iodev->info.name),
"Silent hotword device.");
iodev->info.name[ARRAY_SIZE(iodev->info.name) - 1] =
'\0';
iodev->info.idx = SILENT_HOTWORD_DEVICE;
} else {
snprintf(iodev->info.name, ARRAY_SIZE(iodev->info.name),
"Silent record device.");
iodev->info.name[ARRAY_SIZE(iodev->info.name) - 1] =
'\0';
iodev->info.idx = SILENT_RECORD_DEVICE;
}
} else {
snprintf(iodev->info.name, ARRAY_SIZE(iodev->info.name),
"Silent playback device.");
iodev->info.name[ARRAY_SIZE(iodev->info.name) - 1] = '\0';
iodev->info.idx = SILENT_PLAYBACK_DEVICE;
}
/*
* Record max supported channels into cras_iodev_info.
* The value is the max of empty_supported_channel_counts.
*/
iodev->info.max_supported_channels = 2;
return iodev;
}
void empty_iodev_destroy(struct cras_iodev *iodev)
{
struct empty_iodev *empty_iodev = (struct empty_iodev *)iodev;
if (iodev->direction == CRAS_STREAM_INPUT)
cras_iodev_list_rm_input(iodev);
else
cras_iodev_list_rm_output(iodev);
free(iodev->active_node);
cras_iodev_free_resources(iodev);
free(empty_iodev);
}