blob: ed2df84d2cd8d26b779e8dab45d5301065782cea [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 <stdlib.h>
#include <sys/param.h>
#include <sys/time.h>
#include <syslog.h>
#include <time.h>
#include "buffer_share.h"
#include "cras_audio_area.h"
#include "cras_dsp.h"
#include "cras_dsp_pipeline.h"
#include "cras_iodev.h"
#include "cras_iodev_list.h"
#include "cras_mix.h"
#include "cras_rstream.h"
#include "cras_system_state.h"
#include "cras_util.h"
#include "dev_stream.h"
#include "utlist.h"
#include "rate_estimator.h"
#include "softvol_curve.h"
static const struct timespec rate_estimation_window_sz = {
20, 0 /* 20 sec. */
};
static const double rate_estimation_smooth_factor = 0.9f;
static void cras_iodev_alloc_dsp(struct cras_iodev *iodev);
/*
* Exported Interface.
*/
/* Finds the supported sample rate that best suits the requested rate, "rrate".
* Exact matches have highest priority, then integer multiples, then the default
* rate for the device. */
static size_t get_best_rate(struct cras_iodev *iodev, size_t rrate)
{
size_t i;
size_t best;
if (iodev->supported_rates[0] == 0) /* No rates supported */
return 0;
for (i = 0, best = 0; iodev->supported_rates[i] != 0; i++) {
if (rrate == iodev->supported_rates[i] &&
rrate >= 44100)
return rrate;
if (best == 0 && (rrate % iodev->supported_rates[i] == 0 ||
iodev->supported_rates[i] % rrate == 0))
best = iodev->supported_rates[i];
}
if (best)
return best;
return iodev->supported_rates[0];
}
/* Finds the best match for the channel count. The following match rules
* will apply in order and return the value once matched:
* 1. Match the exact given channel count.
* 2. Match the preferred channel count.
* 3. The first channel count in the list.
*/
static size_t get_best_channel_count(struct cras_iodev *iodev, size_t count)
{
static const size_t preferred_channel_count = 2;
size_t i;
assert(iodev->supported_channel_counts[0] != 0);
for (i = 0; iodev->supported_channel_counts[i] != 0; i++) {
if (iodev->supported_channel_counts[i] == count)
return count;
}
/* If provided count is not supported, search for preferred
* channel count to which we're good at converting.
*/
for (i = 0; iodev->supported_channel_counts[i] != 0; i++) {
if (iodev->supported_channel_counts[i] ==
preferred_channel_count)
return preferred_channel_count;
}
return iodev->supported_channel_counts[0];
}
/* finds the best match for the current format. If no exact match is
* found, use the first. */
static snd_pcm_format_t get_best_pcm_format(struct cras_iodev *iodev,
snd_pcm_format_t fmt)
{
size_t i;
for (i = 0; iodev->supported_formats[i] != 0; i++) {
if (fmt == iodev->supported_formats[i])
return fmt;
}
return iodev->supported_formats[0];
}
/* Set default channel count and layout to an iodev.
* iodev->format->num_channels is from get_best_channel_count.
*/
static void set_default_channel_count_layout(struct cras_iodev *iodev)
{
int8_t default_layout[CRAS_CH_MAX];
size_t i;
for (i = 0; i < CRAS_CH_MAX; i++)
default_layout[i] = i < iodev->format->num_channels ? i : -1;
iodev->ext_format->num_channels = iodev->format->num_channels;
cras_audio_format_set_channel_layout(iodev->format, default_layout);
cras_audio_format_set_channel_layout(iodev->ext_format, default_layout);
}
/* Applies the DSP to the samples for the iodev if applicable. */
static void apply_dsp(struct cras_iodev *iodev, uint8_t *buf, size_t frames)
{
struct cras_dsp_context *ctx;
struct pipeline *pipeline;
ctx = iodev->dsp_context;
if (!ctx)
return;
pipeline = cras_dsp_get_pipeline(ctx);
if (!pipeline)
return;
cras_dsp_pipeline_apply(pipeline,
buf,
frames);
cras_dsp_put_pipeline(ctx);
}
static void cras_iodev_free_dsp(struct cras_iodev *iodev)
{
if (iodev->dsp_context) {
cras_dsp_context_free(iodev->dsp_context);
iodev->dsp_context = NULL;
}
}
/* Modifies the format to the one that will be presented to the device after
* any format changes from the DSP. */
static inline void adjust_dev_fmt_for_dsp(const struct cras_iodev *iodev)
{
struct cras_dsp_context *ctx = iodev->dsp_context;
if (!ctx || !cras_dsp_get_pipeline(ctx))
return;
if (iodev->direction == CRAS_STREAM_OUTPUT) {
iodev->format->num_channels =
cras_dsp_num_output_channels(ctx);
iodev->ext_format->num_channels =
cras_dsp_num_input_channels(ctx);
} else {
iodev->format->num_channels =
cras_dsp_num_input_channels(ctx);
iodev->ext_format->num_channels =
cras_dsp_num_output_channels(ctx);
}
cras_dsp_put_pipeline(ctx);
}
/* Updates channel layout based on the number of channels set by a
* client stream. When successful we need to update the new channel
* layout to ext_format, otherwise we should set a default value
* to both format and ext_format.
*/
static void update_channel_layout(struct cras_iodev *iodev)
{
int rc;
if (iodev->update_channel_layout == NULL)
return;
rc = iodev->update_channel_layout(iodev);
if (rc < 0) {
set_default_channel_count_layout(iodev);
} else {
cras_audio_format_set_channel_layout(
iodev->ext_format,
iodev->format->channel_layout);
}
}
int cras_iodev_set_format(struct cras_iodev *iodev,
const struct cras_audio_format *fmt)
{
size_t actual_rate, actual_num_channels;
snd_pcm_format_t actual_format;
int rc;
/* If this device isn't already using a format, try to match the one
* requested in "fmt". */
if (iodev->format == NULL) {
iodev->format = malloc(sizeof(struct cras_audio_format));
iodev->ext_format = malloc(sizeof(struct cras_audio_format));
if (!iodev->format || !iodev->ext_format)
return -ENOMEM;
*iodev->format = *fmt;
*iodev->ext_format = *fmt;
if (iodev->update_supported_formats) {
rc = iodev->update_supported_formats(iodev);
if (rc) {
syslog(LOG_ERR, "Failed to update formats");
goto error;
}
}
cras_iodev_alloc_dsp(iodev);
if (iodev->dsp_context)
adjust_dev_fmt_for_dsp(iodev);
actual_rate = get_best_rate(iodev, fmt->frame_rate);
actual_num_channels = get_best_channel_count(iodev,
iodev->format->num_channels);
actual_format = get_best_pcm_format(iodev, fmt->format);
if (actual_rate == 0 || actual_num_channels == 0 ||
actual_format == 0) {
/* No compatible frame rate found. */
rc = -EINVAL;
goto error;
}
iodev->format->frame_rate = actual_rate;
iodev->ext_format->frame_rate = actual_rate;
iodev->format->format = actual_format;
iodev->ext_format->format = actual_format;
if (iodev->format->num_channels != actual_num_channels) {
/* If the DSP for this device doesn't match, drop it. */
iodev->format->num_channels = actual_num_channels;
iodev->ext_format->num_channels = actual_num_channels;
cras_iodev_free_dsp(iodev);
}
update_channel_layout(iodev);
if (!iodev->rate_est)
iodev->rate_est = rate_estimator_create(
actual_rate,
&rate_estimation_window_sz,
rate_estimation_smooth_factor);
else
rate_estimator_reset_rate(iodev->rate_est, actual_rate);
}
return 0;
error:
free(iodev->format);
free(iodev->ext_format);
iodev->format = NULL;
iodev->ext_format = NULL;
return rc;
}
void cras_iodev_update_dsp(struct cras_iodev *iodev)
{
if (!iodev->dsp_context)
return;
cras_dsp_set_variable(iodev->dsp_context, "dsp_name",
iodev->dsp_name ? : "");
cras_dsp_load_pipeline(iodev->dsp_context);
}
void cras_iodev_free_format(struct cras_iodev *iodev)
{
free(iodev->format);
free(iodev->ext_format);
iodev->format = NULL;
iodev->ext_format = NULL;
}
void cras_iodev_init_audio_area(struct cras_iodev *iodev,
int num_channels)
{
if (iodev->area)
cras_iodev_free_audio_area(iodev);
iodev->area = cras_audio_area_create(num_channels);
cras_audio_area_config_channels(iodev->area, iodev->format);
}
void cras_iodev_free_audio_area(struct cras_iodev *iodev)
{
if (!iodev->area)
return;
cras_audio_area_destroy(iodev->area);
iodev->area = NULL;
}
void cras_iodev_free_resources(struct cras_iodev *iodev)
{
cras_iodev_free_dsp(iodev);
rate_estimator_destroy(iodev->rate_est);
}
static void cras_iodev_alloc_dsp(struct cras_iodev *iodev)
{
const char *purpose;
if (iodev->direction == CRAS_STREAM_OUTPUT)
purpose = "playback";
else
purpose = "capture";
cras_iodev_free_dsp(iodev);
iodev->dsp_context = cras_dsp_context_new(iodev->ext_format->frame_rate,
purpose);
cras_iodev_update_dsp(iodev);
}
void cras_iodev_fill_time_from_frames(size_t frames,
size_t frame_rate,
struct timespec *ts)
{
uint64_t to_play_usec;
ts->tv_sec = 0;
/* adjust sleep time to target our callback threshold */
to_play_usec = (uint64_t)frames * 1000000L / (uint64_t)frame_rate;
while (to_play_usec > 1000000) {
ts->tv_sec++;
to_play_usec -= 1000000;
}
ts->tv_nsec = to_play_usec * 1000;
}
/* This is called when a node is plugged/unplugged */
static void plug_node(struct cras_ionode *node, int plugged)
{
if (node->plugged == plugged)
return;
node->plugged = plugged;
if (plugged) {
gettimeofday(&node->plugged_time, NULL);
} else {
cras_iodev_list_disable_dev(node->dev);
}
cras_iodev_list_notify_nodes_changed();
}
static void set_node_volume(struct cras_ionode *node, int value)
{
struct cras_iodev *dev = node->dev;
unsigned int volume;
if (dev->direction != CRAS_STREAM_OUTPUT)
return;
volume = (unsigned int)MIN(value, 100);
node->volume = volume;
if (dev->set_volume)
dev->set_volume(dev);
cras_iodev_list_notify_node_volume(node);
}
static void set_node_capture_gain(struct cras_ionode *node, int value)
{
struct cras_iodev *dev = node->dev;
if (dev->direction != CRAS_STREAM_INPUT)
return;
node->capture_gain = (long)value;
if (dev->set_capture_gain)
dev->set_capture_gain(dev);
cras_iodev_list_notify_node_capture_gain(node);
}
static void set_node_left_right_swapped(struct cras_ionode *node, int value)
{
struct cras_iodev *dev = node->dev;
int rc;
if (!dev->set_swap_mode_for_node)
return;
rc = dev->set_swap_mode_for_node(dev, node, value);
if (rc) {
syslog(LOG_ERR,
"Failed to set swap mode on node %s to %d; error %d",
node->name, value, rc);
return;
}
node->left_right_swapped = value;
cras_iodev_list_notify_node_left_right_swapped(node);
return;
}
int cras_iodev_set_node_attr(struct cras_ionode *ionode,
enum ionode_attr attr, int value)
{
switch (attr) {
case IONODE_ATTR_PLUGGED:
plug_node(ionode, value);
break;
case IONODE_ATTR_VOLUME:
set_node_volume(ionode, value);
break;
case IONODE_ATTR_CAPTURE_GAIN:
set_node_capture_gain(ionode, value);
break;
case IONODE_ATTR_SWAP_LEFT_RIGHT:
set_node_left_right_swapped(ionode, value);
break;
default:
return -EINVAL;
}
return 0;
}
void cras_iodev_add_node(struct cras_iodev *iodev, struct cras_ionode *node)
{
DL_APPEND(iodev->nodes, node);
cras_iodev_list_notify_nodes_changed();
}
void cras_iodev_rm_node(struct cras_iodev *iodev, struct cras_ionode *node)
{
DL_DELETE(iodev->nodes, node);
cras_iodev_list_notify_nodes_changed();
}
void cras_iodev_set_active_node(struct cras_iodev *iodev,
struct cras_ionode *node)
{
iodev->active_node = node;
cras_iodev_list_notify_active_node_changed();
}
float cras_iodev_get_software_volume_scaler(struct cras_iodev *iodev)
{
unsigned int volume;
volume = cras_iodev_adjust_active_node_volume(
iodev, cras_system_get_volume());
if (iodev->active_node && iodev->active_node->softvol_scalers)
return iodev->active_node->softvol_scalers[volume];
return softvol_get_scaler(volume);
}
int cras_iodev_add_stream(struct cras_iodev *iodev,
struct dev_stream *stream)
{
unsigned int cb_threshold = dev_stream_cb_threshold(stream);
DL_APPEND(iodev->streams, stream);
if (!iodev->buf_state)
iodev->buf_state = buffer_share_create(iodev->buffer_size);
buffer_share_add_id(iodev->buf_state, stream->stream->stream_id, NULL);
iodev->min_cb_level = MIN(iodev->min_cb_level, cb_threshold);
iodev->max_cb_level = MAX(iodev->max_cb_level, cb_threshold);
return 0;
}
struct dev_stream *cras_iodev_rm_stream(struct cras_iodev *iodev,
const struct cras_rstream *rstream)
{
struct dev_stream *out;
struct dev_stream *ret = NULL;
unsigned int cb_threshold;
unsigned int old_min_cb_level = iodev->min_cb_level;
iodev->min_cb_level = iodev->buffer_size / 2;
iodev->max_cb_level = 0;
DL_FOREACH(iodev->streams, out) {
if (out->stream == rstream) {
buffer_share_rm_id(iodev->buf_state,
rstream->stream_id);
ret = out;
DL_DELETE(iodev->streams, out);
continue;
}
cb_threshold = dev_stream_cb_threshold(out);
iodev->min_cb_level = MIN(iodev->min_cb_level, cb_threshold);
iodev->max_cb_level = MAX(iodev->max_cb_level, cb_threshold);
}
if (!iodev->streams) {
buffer_share_destroy(iodev->buf_state);
iodev->buf_state = NULL;
iodev->min_cb_level = old_min_cb_level;
}
return ret;
}
unsigned int cras_iodev_stream_offset(struct cras_iodev *iodev,
struct dev_stream *stream)
{
return buffer_share_id_offset(iodev->buf_state,
stream->stream->stream_id);
}
void cras_iodev_stream_written(struct cras_iodev *iodev,
struct dev_stream *stream,
unsigned int nwritten)
{
buffer_share_offset_update(iodev->buf_state,
stream->stream->stream_id, nwritten);
}
unsigned int cras_iodev_all_streams_written(struct cras_iodev *iodev)
{
if (!iodev->buf_state)
return 0;
return buffer_share_get_new_write_point(iodev->buf_state);
}
unsigned int cras_iodev_max_stream_offset(const struct cras_iodev *iodev)
{
unsigned int max = 0;
struct dev_stream *curr;
DL_FOREACH(iodev->streams, curr) {
max = MAX(max,
buffer_share_id_offset(iodev->buf_state,
curr->stream->stream_id));
}
return max;
}
int cras_iodev_open(struct cras_iodev *iodev, unsigned int cb_level)
{
int rc;
rc = iodev->open_dev(iodev);
if (rc < 0)
return rc;
/* Make sure the min_cb_level doesn't get too large. */
iodev->min_cb_level = MIN(iodev->buffer_size / 2, cb_level);
iodev->max_cb_level = 0;
return 0;
}
int cras_iodev_close(struct cras_iodev *iodev)
{
if (!iodev->is_open(iodev))
return 0;
return iodev->close_dev(iodev);
}
int cras_iodev_put_input_buffer(struct cras_iodev *iodev, unsigned int nframes)
{
rate_estimator_add_frames(iodev->rate_est, -nframes);
return iodev->put_buffer(iodev, nframes);
}
int cras_iodev_put_output_buffer(struct cras_iodev *iodev, uint8_t *frames,
unsigned int nframes)
{
const struct cras_audio_format *fmt = iodev->format;
if (iodev->pre_dsp_hook)
iodev->pre_dsp_hook(frames, nframes, iodev->ext_format,
iodev->pre_dsp_hook_cb_data);
if (cras_system_get_mute()) {
const unsigned int frame_bytes = cras_get_format_bytes(fmt);
cras_mix_mute_buffer(frames, frame_bytes, nframes);
} else {
apply_dsp(iodev, frames, nframes);
if (iodev->post_dsp_hook)
iodev->post_dsp_hook(frames, nframes, fmt,
iodev->post_dsp_hook_cb_data);
if (cras_iodev_software_volume_needed(iodev)) {
unsigned int nsamples = nframes * fmt->num_channels;
float scaler =
cras_iodev_get_software_volume_scaler(iodev);
cras_scale_buffer(fmt->format, frames,
nsamples, scaler);
}
}
rate_estimator_add_frames(iodev->rate_est, nframes);
return iodev->put_buffer(iodev, nframes);
}
int cras_iodev_get_input_buffer(struct cras_iodev *iodev,
struct cras_audio_area **area,
unsigned *frames)
{
const struct cras_audio_format *fmt = iodev->format;
const unsigned int frame_bytes = cras_get_format_bytes(fmt);
uint8_t *hw_buffer;
int rc;
rc = iodev->get_buffer(iodev, area, frames);
if (rc < 0 || *frames == 0)
return rc;
/* TODO(dgreid) - This assumes interleaved audio. */
hw_buffer = (*area)->channels[0].buf;
if (cras_system_get_capture_mute())
cras_mix_mute_buffer(hw_buffer, frame_bytes, *frames);
else
apply_dsp(iodev, hw_buffer, *frames); /* TODO-applied 2x */
return rc;
}
int cras_iodev_get_output_buffer(struct cras_iodev *iodev,
struct cras_audio_area **area,
unsigned *frames)
{
return iodev->get_buffer(iodev, area, frames);
}
int cras_iodev_update_rate(struct cras_iodev *iodev, unsigned int level)
{
struct timespec now;
clock_gettime(CLOCK_MONOTONIC_RAW, &now);
return rate_estimator_check(iodev->rate_est, level, &now);
}
int cras_iodev_reset_rate_estimator(const struct cras_iodev *iodev)
{
rate_estimator_reset_rate(iodev->rate_est,
iodev->ext_format->frame_rate);
return 0;
}
double cras_iodev_get_est_rate_ratio(const struct cras_iodev *iodev)
{
return rate_estimator_get_rate(iodev->rate_est) /
iodev->ext_format->frame_rate;
}
int cras_iodev_get_dsp_delay(const struct cras_iodev *iodev)
{
struct cras_dsp_context *ctx;
struct pipeline *pipeline;
int delay;
ctx = iodev->dsp_context;
if (!ctx)
return 0;
pipeline = cras_dsp_get_pipeline(ctx);
if (!pipeline)
return 0;
delay = cras_dsp_pipeline_get_delay(pipeline);
cras_dsp_put_pipeline(ctx);
return delay;
}
int cras_iodev_frames_queued(struct cras_iodev *iodev)
{
int rc;
rc = iodev->frames_queued(iodev);
if (rc < 0 || iodev->direction == CRAS_STREAM_INPUT)
return rc;
if (rc < iodev->min_buffer_level)
return 0;
return rc - iodev->min_buffer_level;
}
int cras_iodev_buffer_avail(struct cras_iodev *iodev, unsigned hw_level)
{
if (iodev->direction == CRAS_STREAM_INPUT)
return hw_level;
if (hw_level + iodev->min_buffer_level > iodev->buffer_size)
return 0;
return iodev->buffer_size - iodev->min_buffer_level - hw_level;
}
void cras_iodev_register_pre_dsp_hook(struct cras_iodev *iodev,
loopback_hook_t loop_cb,
void *cb_data)
{
iodev->pre_dsp_hook = loop_cb;
iodev->pre_dsp_hook_cb_data = cb_data;
}
void cras_iodev_register_post_dsp_hook(struct cras_iodev *iodev,
loopback_hook_t loop_cb,
void *cb_data)
{
iodev->post_dsp_hook = loop_cb;
iodev->post_dsp_hook_cb_data = cb_data;
}